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.Runtime.InteropServices; 6 using System.Diagnostics; 7 using System.DirectoryServices.Interop; 8 using System.ComponentModel; 9 using System.Threading; 10 using System.Reflection; 11 using System.Security.Permissions; 12 using System.DirectoryServices.Design; 13 using System.Globalization; 14 using System.Net; 15 16 namespace System.DirectoryServices 17 { 18 /// <devdoc> 19 /// Encapsulates a node or an object in the Active Directory hierarchy. 20 /// </devdoc> 21 [ 22 TypeConverterAttribute(typeof(DirectoryEntryConverter)) 23 ] 24 public class DirectoryEntry : Component 25 { 26 private string _path = ""; 27 private UnsafeNativeMethods.IAds _adsObject; 28 private bool _useCache = true; 29 private bool _cacheFilled; 30 // disable csharp compiler warning #0414: field assigned unused value 31 #pragma warning disable 0414 32 internal bool propertiesAlreadyEnumerated = false; 33 #pragma warning restore 0414 34 private bool _disposed = false; 35 private AuthenticationTypes _authenticationType = AuthenticationTypes.Secure; 36 private NetworkCredential _credentials; 37 private readonly DirectoryEntryConfiguration _options; 38 39 private PropertyCollection _propertyCollection = null; 40 internal bool allowMultipleChange = false; 41 private bool _userNameIsNull = false; 42 private bool _passwordIsNull = false; 43 private bool _objectSecurityInitialized = false; 44 private bool _objectSecurityModified = false; 45 private ActiveDirectorySecurity _objectSecurity = null; 46 private static string s_securityDescriptorProperty = "ntSecurityDescriptor"; 47 48 /// <devdoc> 49 /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectoryEntry'/>class. 50 /// </devdoc> DirectoryEntry()51 public DirectoryEntry() 52 { 53 _options = new DirectoryEntryConfiguration(this); 54 } 55 56 /// <devdoc> 57 /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectoryEntry'/> class that will bind 58 /// to the directory entry at <paramref name="path"/>. 59 /// </devdoc> DirectoryEntry(string path)60 public DirectoryEntry(string path) : this() 61 { 62 Path = path; 63 } 64 65 /// <devdoc> 66 /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectoryEntry'/> class. 67 /// </devdoc> DirectoryEntry(string path, string username, string password)68 public DirectoryEntry(string path, string username, string password) : this(path, username, password, AuthenticationTypes.Secure) 69 { 70 } 71 72 /// <devdoc> 73 /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectoryEntry'/> class. 74 /// </devdoc> DirectoryEntry(string path, string username, string password, AuthenticationTypes authenticationType)75 public DirectoryEntry(string path, string username, string password, AuthenticationTypes authenticationType) : this(path) 76 { 77 _credentials = new NetworkCredential(username, password); 78 if (username == null) 79 _userNameIsNull = true; 80 81 if (password == null) 82 _passwordIsNull = true; 83 84 _authenticationType = authenticationType; 85 } 86 DirectoryEntry(string path, bool useCache, string username, string password, AuthenticationTypes authenticationType)87 internal DirectoryEntry(string path, bool useCache, string username, string password, AuthenticationTypes authenticationType) 88 { 89 _path = path; 90 _useCache = useCache; 91 _credentials = new NetworkCredential(username, password); 92 if (username == null) 93 _userNameIsNull = true; 94 95 if (password == null) 96 _passwordIsNull = true; 97 98 _authenticationType = authenticationType; 99 100 _options = new DirectoryEntryConfiguration(this); 101 } 102 103 /// <devdoc> 104 /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectoryEntry'/> class that will bind 105 /// to the native Active Directory object which is passed in. 106 /// </devdoc> DirectoryEntry(object adsObject)107 public DirectoryEntry(object adsObject) 108 : this(adsObject, true, null, null, AuthenticationTypes.Secure, true) 109 { 110 } 111 DirectoryEntry(object adsObject, bool useCache, string username, string password, AuthenticationTypes authenticationType)112 internal DirectoryEntry(object adsObject, bool useCache, string username, string password, AuthenticationTypes authenticationType) 113 : this(adsObject, useCache, username, password, authenticationType, false) 114 { 115 } 116 DirectoryEntry(object adsObject, bool useCache, string username, string password, AuthenticationTypes authenticationType, bool AdsObjIsExternal)117 internal DirectoryEntry(object adsObject, bool useCache, string username, string password, AuthenticationTypes authenticationType, bool AdsObjIsExternal) 118 { 119 _adsObject = adsObject as UnsafeNativeMethods.IAds; 120 if (_adsObject == null) 121 throw new ArgumentException(SR.DSDoesNotImplementIADs); 122 123 // GetInfo is not needed here. ADSI executes an implicit GetInfo when GetEx 124 // is called on the PropertyValueCollection. 0x800704BC error might be returned 125 // on some WinNT entries, when iterating through 'Users' group members. 126 // if (forceBind) 127 // this.adsObject.GetInfo(); 128 _path = _adsObject.ADsPath; 129 _useCache = useCache; 130 131 _authenticationType = authenticationType; 132 _credentials = new NetworkCredential(username, password); 133 if (username == null) 134 _userNameIsNull = true; 135 136 if (password == null) 137 _passwordIsNull = true; 138 139 if (!useCache) 140 CommitChanges(); 141 142 _options = new DirectoryEntryConfiguration(this); 143 144 // We are starting from an already bound connection so make sure the options are set properly. 145 // If this is an externallly managed com object then we don't want to change it's current behavior 146 if (!AdsObjIsExternal) 147 { 148 InitADsObjectOptions(); 149 } 150 } 151 152 internal UnsafeNativeMethods.IAds AdsObject 153 { 154 get 155 { 156 Bind(); 157 return _adsObject; 158 } 159 } 160 161 [DefaultValue(AuthenticationTypes.Secure)] 162 public AuthenticationTypes AuthenticationType 163 { 164 get => _authenticationType; 165 set 166 { 167 if (_authenticationType == value) 168 return; 169 170 _authenticationType = value; 171 Unbind(); 172 } 173 } 174 175 private bool Bound => _adsObject != null; 176 177 /// <devdoc> 178 /// Gets a <see cref='System.DirectoryServices.DirectoryEntries'/> 179 /// containing the child entries of this node in the Active 180 /// Directory hierarchy. 181 /// </devdoc> 182 public DirectoryEntries Children => new DirectoryEntries(this); 183 184 internal UnsafeNativeMethods.IAdsContainer ContainerObject 185 { 186 get 187 { 188 Bind(); 189 return (UnsafeNativeMethods.IAdsContainer)_adsObject; 190 } 191 } 192 193 /// <devdoc> 194 /// Gets the globally unique identifier of the <see cref='System.DirectoryServices.DirectoryEntry'/>. 195 /// </devdoc> 196 public Guid Guid 197 { 198 get 199 { 200 string guid = NativeGuid; 201 if (guid.Length == 32) 202 { 203 // oddly, the value comes back as a string with no dashes from LDAP 204 byte[] intGuid = new byte[16]; 205 for (int j = 0; j < 16; j++) 206 { 207 intGuid[j] = Convert.ToByte(new String(new char[] { guid[j * 2], guid[j * 2 + 1] }), 16); 208 } 209 return new Guid(intGuid); 210 // return new Guid(guid.Substring(0, 8) + "-" + guid.Substring(8, 4) + "-" + guid.Substring(12, 4) + "-" + guid.Substring(16, 4) + "-" + guid.Substring(20)); 211 } 212 else 213 return new Guid(guid); 214 } 215 } 216 217 public ActiveDirectorySecurity ObjectSecurity 218 { 219 get 220 { 221 if (!_objectSecurityInitialized) 222 { 223 _objectSecurity = GetObjectSecurityFromCache(); 224 _objectSecurityInitialized = true; 225 } 226 227 return _objectSecurity; 228 } 229 set 230 { 231 if (value == null) 232 { 233 throw new ArgumentNullException("value"); 234 } 235 236 _objectSecurity = value; 237 _objectSecurityInitialized = true; 238 _objectSecurityModified = true; 239 240 CommitIfNotCaching(); 241 } 242 } 243 244 internal bool IsContainer 245 { 246 get 247 { 248 Bind(); 249 return _adsObject is UnsafeNativeMethods.IAdsContainer; 250 } 251 } 252 253 internal bool JustCreated { get; set; } 254 255 /// <devdoc> 256 /// Gets the relative name of the object as named with the underlying directory service. 257 /// </devdoc> 258 public string Name 259 { 260 get 261 { 262 Bind(); 263 string tmpName = _adsObject.Name; 264 GC.KeepAlive(this); 265 return tmpName; 266 } 267 } 268 269 public string NativeGuid 270 { 271 get 272 { 273 FillCache("GUID"); 274 string tmpGuid = _adsObject.GUID; 275 GC.KeepAlive(this); 276 return tmpGuid; 277 } 278 } 279 280 /// <devdoc> 281 /// Gets the native Active Directory Services Interface (ADSI) object. 282 /// </devdoc> 283 public object NativeObject 284 { 285 get 286 { 287 Bind(); 288 return _adsObject; 289 } 290 } 291 292 /// <devdoc> 293 /// Gets this entry's parent entry in the Active Directory hierarchy. 294 /// </devdoc> 295 public DirectoryEntry Parent 296 { 297 get 298 { 299 Bind(); 300 return new DirectoryEntry(_adsObject.Parent, UsePropertyCache, GetUsername(), GetPassword(), AuthenticationType); 301 } 302 } 303 304 /// <devdoc> 305 /// Gets or sets the password to use when authenticating the client. 306 /// </devdoc> 307 [DefaultValue(null)] 308 public string Password 309 { 310 set 311 { 312 if (value == GetPassword()) 313 return; 314 315 if (_credentials == null) 316 { 317 _credentials = new NetworkCredential(); 318 // have not set it yet 319 _userNameIsNull = true; 320 } 321 322 if (value == null) 323 _passwordIsNull = true; 324 else 325 _passwordIsNull = false; 326 327 _credentials.Password = value; 328 329 Unbind(); 330 } 331 } 332 333 /// <devdoc> 334 /// Gets or sets the path for this <see cref='System.DirectoryServices.DirectoryEntry'/>. 335 /// </devdoc> 336 [ 337 DefaultValue(""), 338 // CoreFXPort - Remove design support 339 // TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign) 340 ] 341 public string Path 342 { 343 get => _path; 344 set 345 { 346 if (value == null) 347 value = ""; 348 349 if (System.DirectoryServices.ActiveDirectory.Utils.Compare(_path, value) == 0) 350 return; 351 352 _path = value; 353 Unbind(); 354 } 355 } 356 357 /// <devdoc> 358 /// Gets a <see cref='System.DirectoryServices.PropertyCollection'/> of properties set on this object. 359 /// </devdoc> 360 public PropertyCollection Properties 361 { 362 get 363 { 364 if (_propertyCollection == null) 365 { 366 _propertyCollection = new PropertyCollection(this); 367 } 368 369 return _propertyCollection; 370 } 371 } 372 373 /// <devdoc> 374 /// Gets the name of the schema used for this <see cref='System.DirectoryServices.DirectoryEntry'/> 375 /// </devdoc> 376 public string SchemaClassName 377 { 378 get 379 { 380 Bind(); 381 string tmpClass = _adsObject.Class; 382 GC.KeepAlive(this); 383 return tmpClass; 384 } 385 } 386 387 /// <devdoc> 388 /// Gets the <see cref='System.DirectoryServices.DirectoryEntry'/> that holds schema information for this 389 /// entry. An entry's <see cref='System.DirectoryServices.DirectoryEntry.SchemaClassName'/> 390 /// determines what properties are valid for it.</para> 391 /// </devdoc> 392 public DirectoryEntry SchemaEntry 393 { 394 get 395 { 396 Bind(); 397 return new DirectoryEntry(_adsObject.Schema, UsePropertyCache, GetUsername(), GetPassword(), AuthenticationType); 398 } 399 } 400 401 // By default changes to properties are done locally to 402 // a cache and reading property values is cached after 403 // the first read. Setting this to false will cause the 404 // cache to be committed after each operation. 405 // 406 /// <devdoc> 407 /// Gets a value indicating whether the cache should be committed after each 408 /// operation. 409 /// </devdoc> 410 [DefaultValue(true)] 411 public bool UsePropertyCache 412 { 413 get => _useCache; 414 set 415 { 416 if (value == _useCache) 417 return; 418 419 // auto-commit when they set this to false. 420 if (!value) 421 CommitChanges(); 422 423 _cacheFilled = false; // cache mode has been changed 424 _useCache = value; 425 } 426 } 427 428 /// <devdoc> 429 /// Gets or sets the username to use when authenticating the client.</para> 430 /// </devdoc> 431 [ 432 DefaultValue(null), 433 // CoreFXPort - Remove design support 434 // TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign) 435 ] 436 public string Username 437 { 438 get 439 { 440 if (_credentials == null || _userNameIsNull) 441 return null; 442 443 return _credentials.UserName; 444 } 445 set 446 { 447 if (value == GetUsername()) 448 return; 449 450 if (_credentials == null) 451 { 452 _credentials = new NetworkCredential(); 453 _passwordIsNull = true; 454 } 455 456 if (value == null) 457 _userNameIsNull = true; 458 else 459 _userNameIsNull = false; 460 461 _credentials.UserName = value; 462 463 Unbind(); 464 } 465 } 466 467 public DirectoryEntryConfiguration Options 468 { 469 get 470 { 471 // only LDAP provider supports IADsObjectOptions, so make the check here 472 if (!(AdsObject is UnsafeNativeMethods.IAdsObjectOptions)) 473 return null; 474 475 return _options; 476 } 477 } 478 InitADsObjectOptions()479 internal void InitADsObjectOptions() 480 { 481 if (_adsObject is UnsafeNativeMethods.IAdsObjectOptions2) 482 { 483 //-------------------------------------------- 484 // Check if ACCUMULATE_MODIFICATION is available 485 //-------------------------------------------- 486 object o = null; 487 int unmanagedResult = 0; 488 // check whether the new option is available 489 490 // 8 is ADS_OPTION_ACCUMULATIVE_MODIFICATION 491 unmanagedResult = ((UnsafeNativeMethods.IAdsObjectOptions2)_adsObject).GetOption(8, out o); 492 if (unmanagedResult != 0) 493 { 494 // rootdse does not support this option and invalid parameter due to without accumulative change fix in ADSI 495 if ((unmanagedResult == unchecked((int)0x80004001)) || (unmanagedResult == unchecked((int)0x80005008))) 496 { 497 return; 498 } 499 else 500 { 501 throw COMExceptionHelper.CreateFormattedComException(unmanagedResult); 502 } 503 } 504 505 // the new option is available, set it so we get the new PutEx behavior that will allow multiple changes 506 Variant value = new Variant(); 507 value.varType = 11; //VT_BOOL 508 value.boolvalue = -1; 509 ((UnsafeNativeMethods.IAdsObjectOptions2)_adsObject).SetOption(8, value); 510 511 allowMultipleChange = true; 512 } 513 } 514 515 /// <devdoc> 516 /// Binds to the ADs object (if not already bound). 517 /// </devdoc> Bind()518 private void Bind() 519 { 520 Bind(true); 521 } 522 Bind(bool throwIfFail)523 internal void Bind(bool throwIfFail) 524 { 525 //Cannot rebind after the object has been disposed, since finalization has been suppressed. 526 527 if (_disposed) 528 throw new ObjectDisposedException(GetType().Name); 529 530 if (_adsObject == null) 531 { 532 string pathToUse = Path; 533 if (pathToUse == null || pathToUse.Length == 0) 534 { 535 // get the default naming context. This should be the default root for the search. 536 DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE", true, null, null, AuthenticationTypes.Secure); 537 538 //SECREVIEW: Looking at the root of the DS will demand browse permissions 539 // on "*" or "LDAP://RootDSE". 540 string defaultNamingContext = (string)rootDSE.Properties["defaultNamingContext"][0]; 541 rootDSE.Dispose(); 542 543 pathToUse = "LDAP://" + defaultNamingContext; 544 } 545 546 // Ensure we've got a thread model set, else CoInitialize() won't have been called. 547 if (Thread.CurrentThread.GetApartmentState() == ApartmentState.Unknown) 548 Thread.CurrentThread.SetApartmentState(ApartmentState.MTA); 549 550 Guid g = new Guid("00000000-0000-0000-c000-000000000046"); // IID_IUnknown 551 object value = null; 552 int hr = UnsafeNativeMethods.ADsOpenObject(pathToUse, GetUsername(), GetPassword(), (int)_authenticationType, ref g, out value); 553 554 if (hr != 0) 555 { 556 if (throwIfFail) 557 throw COMExceptionHelper.CreateFormattedComException(hr); 558 } 559 else 560 { 561 _adsObject = (UnsafeNativeMethods.IAds)value; 562 } 563 564 InitADsObjectOptions(); 565 } 566 } 567 568 // Create new entry with the same data, but different IADs object, and grant it Browse Permission. CloneBrowsable()569 internal DirectoryEntry CloneBrowsable() 570 { 571 DirectoryEntry newEntry = new DirectoryEntry(this.Path, this.UsePropertyCache, this.GetUsername(), this.GetPassword(), this.AuthenticationType); 572 return newEntry; 573 } 574 575 /// <devdoc> 576 /// Closes the <see cref='System.DirectoryServices.DirectoryEntry'/> 577 /// and releases any system resources associated with this component. 578 /// </devdoc> Close()579 public void Close() 580 { 581 Unbind(); 582 } 583 584 /// <devdoc> 585 /// Saves any changes to the entry in the directory store. 586 /// </devdoc> CommitChanges()587 public void CommitChanges() 588 { 589 if (JustCreated) 590 { 591 // Note: Permissions Demand is not necessary here, because entry has already been created with appr. permissions. 592 // Write changes regardless of Caching mode to finish construction of a new entry. 593 try 594 { 595 // 596 // Write the security descriptor to the cache 597 // 598 SetObjectSecurityInCache(); 599 600 _adsObject.SetInfo(); 601 } 602 catch (COMException e) 603 { 604 throw COMExceptionHelper.CreateFormattedComException(e); 605 } 606 JustCreated = false; 607 _objectSecurityInitialized = false; 608 _objectSecurityModified = false; 609 610 // we need to refresh that properties table. 611 _propertyCollection = null; 612 return; 613 } 614 if (!_useCache) 615 { 616 // unless we have modified the existing security descriptor (in-place) through ObjectSecurity property 617 // there is nothing to do 618 if ((_objectSecurity == null) || (!_objectSecurity.IsModified())) 619 { 620 return; 621 } 622 } 623 624 if (!Bound) 625 return; 626 627 try 628 { 629 // 630 // Write the security descriptor to the cache 631 // 632 SetObjectSecurityInCache(); 633 _adsObject.SetInfo(); 634 _objectSecurityInitialized = false; 635 _objectSecurityModified = false; 636 } 637 catch (COMException e) 638 { 639 throw COMExceptionHelper.CreateFormattedComException(e); 640 } 641 // we need to refresh that properties table. 642 _propertyCollection = null; 643 } 644 CommitIfNotCaching()645 internal void CommitIfNotCaching() 646 { 647 if (JustCreated) 648 return; // Do not write changes, beacuse the entry is just under construction until CommitChanges() is called. 649 650 if (_useCache) 651 return; 652 653 if (!Bound) 654 return; 655 656 try 657 { 658 // 659 // Write the security descriptor to the cache 660 // 661 SetObjectSecurityInCache(); 662 663 _adsObject.SetInfo(); 664 _objectSecurityInitialized = false; 665 _objectSecurityModified = false; 666 } 667 catch (COMException e) 668 { 669 throw COMExceptionHelper.CreateFormattedComException(e); 670 } 671 // we need to refresh that properties table. 672 _propertyCollection = null; 673 } 674 675 /// <devdoc> 676 /// Creates a copy of this entry as a child of the given parent. 677 /// </devdoc> 678 public DirectoryEntry CopyTo(DirectoryEntry newParent) => CopyTo(newParent, null); 679 680 /// <devdoc> 681 /// Creates a copy of this entry as a child of the given parent and gives it a new name. 682 /// </devdoc> CopyTo(DirectoryEntry newParent, string newName)683 public DirectoryEntry CopyTo(DirectoryEntry newParent, string newName) 684 { 685 if (!newParent.IsContainer) 686 throw new InvalidOperationException(SR.Format(SR.DSNotAContainer , newParent.Path)); 687 688 object copy = null; 689 try 690 { 691 copy = newParent.ContainerObject.CopyHere(Path, newName); 692 } 693 catch (COMException e) 694 { 695 throw COMExceptionHelper.CreateFormattedComException(e); 696 } 697 return new DirectoryEntry(copy, newParent.UsePropertyCache, GetUsername(), GetPassword(), AuthenticationType); 698 } 699 700 /// <devdoc> 701 /// Deletes this entry and its entire subtree from the Active Directory hierarchy. 702 /// </devdoc> DeleteTree()703 public void DeleteTree() 704 { 705 if (!(AdsObject is UnsafeNativeMethods.IAdsDeleteOps)) 706 throw new InvalidOperationException(SR.DSCannotDelete); 707 708 UnsafeNativeMethods.IAdsDeleteOps entry = (UnsafeNativeMethods.IAdsDeleteOps)AdsObject; 709 try 710 { 711 entry.DeleteObject(0); 712 } 713 catch (COMException e) 714 { 715 throw COMExceptionHelper.CreateFormattedComException(e); 716 } 717 718 GC.KeepAlive(this); 719 } 720 Dispose(bool disposing)721 protected override void Dispose(bool disposing) 722 { 723 // no managed object to free 724 725 // free own state (unmanaged objects) 726 if (!_disposed) 727 { 728 Unbind(); 729 _disposed = true; 730 } 731 732 base.Dispose(disposing); 733 } 734 735 /// <devdoc> 736 /// Searches the directory store at the given path to see whether an entry exists. 737 /// </devdoc> Exists(string path)738 public static bool Exists(string path) 739 { 740 DirectoryEntry entry = new DirectoryEntry(path); 741 try 742 { 743 entry.Bind(true); // throws exceptions (possibly can break applications) 744 return entry.Bound; 745 } 746 catch (System.Runtime.InteropServices.COMException e) 747 { 748 if (e.ErrorCode == unchecked((int)0x80072030) || 749 e.ErrorCode == unchecked((int)0x80070003) || // ERROR_DS_NO_SUCH_OBJECT and path not found (not found in strict sense) 750 e.ErrorCode == unchecked((int)0x800708AC)) // Group name could not be found 751 return false; 752 throw; 753 } 754 finally 755 { 756 entry.Dispose(); 757 } 758 } 759 760 /// <devdoc> 761 /// If UsePropertyCache is true, calls GetInfo the first time it's necessary. 762 /// If it's false, calls GetInfoEx on the given property name. 763 /// </devdoc> FillCache(string propertyName)764 internal void FillCache(string propertyName) 765 { 766 if (UsePropertyCache) 767 { 768 if (_cacheFilled) 769 return; 770 771 RefreshCache(); 772 _cacheFilled = true; 773 } 774 else 775 { 776 Bind(); 777 try 778 { 779 if (propertyName.Length > 0) 780 _adsObject.GetInfoEx(new object[] { propertyName }, 0); 781 else 782 _adsObject.GetInfo(); 783 } 784 catch (COMException e) 785 { 786 throw COMExceptionHelper.CreateFormattedComException(e); 787 } 788 } 789 } 790 791 /// <devdoc> 792 /// Calls a method on the native Active Directory. 793 /// </devdoc> Invoke(string methodName, params object[] args)794 public object Invoke(string methodName, params object[] args) 795 { 796 object target = this.NativeObject; 797 Type type = target.GetType(); 798 object result = null; 799 try 800 { 801 result = type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, target, args, CultureInfo.InvariantCulture); 802 GC.KeepAlive(this); 803 } 804 catch (COMException e) 805 { 806 throw COMExceptionHelper.CreateFormattedComException(e); 807 } 808 catch (TargetInvocationException e) 809 { 810 if (e.InnerException != null) 811 { 812 if (e.InnerException is COMException) 813 { 814 COMException inner = (COMException)e.InnerException; 815 throw new TargetInvocationException(e.Message, COMExceptionHelper.CreateFormattedComException(inner)); 816 } 817 } 818 819 throw e; 820 } 821 822 if (result is UnsafeNativeMethods.IAds) 823 824 return new DirectoryEntry(result, UsePropertyCache, GetUsername(), GetPassword(), AuthenticationType); 825 else 826 return result; 827 } 828 829 /// <devdoc> 830 /// Reads a property on the native Active Directory object. 831 /// </devdoc> InvokeGet(string propertyName)832 public object InvokeGet(string propertyName) 833 { 834 object target = this.NativeObject; 835 Type type = target.GetType(); 836 object result = null; 837 try 838 { 839 result = type.InvokeMember(propertyName, BindingFlags.GetProperty, null, target, null, CultureInfo.InvariantCulture); 840 GC.KeepAlive(this); 841 } 842 catch (COMException e) 843 { 844 throw COMExceptionHelper.CreateFormattedComException(e); 845 } 846 catch (TargetInvocationException e) 847 { 848 if (e.InnerException != null) 849 { 850 if (e.InnerException is COMException) 851 { 852 COMException inner = (COMException)e.InnerException; 853 throw new TargetInvocationException(e.Message, COMExceptionHelper.CreateFormattedComException(inner)); 854 } 855 } 856 857 throw e; 858 } 859 860 return result; 861 } 862 863 /// <devdoc> 864 /// Sets a property on the native Active Directory object. 865 /// </devdoc> InvokeSet(string propertyName, params object[] args)866 public void InvokeSet(string propertyName, params object[] args) 867 { 868 object target = this.NativeObject; 869 Type type = target.GetType(); 870 try 871 { 872 type.InvokeMember(propertyName, BindingFlags.SetProperty, null, target, args, CultureInfo.InvariantCulture); 873 GC.KeepAlive(this); 874 } 875 catch (COMException e) 876 { 877 throw COMExceptionHelper.CreateFormattedComException(e); 878 } 879 catch (TargetInvocationException e) 880 { 881 if (e.InnerException != null) 882 { 883 if (e.InnerException is COMException) 884 { 885 COMException inner = (COMException)e.InnerException; 886 throw new TargetInvocationException(e.Message, COMExceptionHelper.CreateFormattedComException(inner)); 887 } 888 } 889 890 throw e; 891 } 892 } 893 894 /// <devdoc> 895 /// Moves this entry to the given parent. 896 /// </devdoc> 897 public void MoveTo(DirectoryEntry newParent) => MoveTo(newParent, null); 898 899 /// <devdoc> 900 /// Moves this entry to the given parent, and gives it a new name. 901 /// </devdoc> MoveTo(DirectoryEntry newParent, string newName)902 public void MoveTo(DirectoryEntry newParent, string newName) 903 { 904 object newEntry = null; 905 if (!(newParent.AdsObject is UnsafeNativeMethods.IAdsContainer)) 906 throw new InvalidOperationException(SR.Format(SR.DSNotAContainer , newParent.Path)); 907 try 908 { 909 if (AdsObject.ADsPath.StartsWith("WinNT:", StringComparison.Ordinal)) 910 { 911 // get the ADsPath instead of using Path as ADsPath for the case that "WinNT://computername" is passed in while we need "WinNT://domain/computer" 912 string childPath = AdsObject.ADsPath; 913 string parentPath = newParent.AdsObject.ADsPath; 914 915 // we know ADsPath does not end with object type qualifier like ",computer" so it is fine to compare with whole newparent's adspath 916 // for the case that child has different components from newparent in the aspects other than case, we don't do any processing, just let ADSI decide in case future adsi change 917 if (System.DirectoryServices.ActiveDirectory.Utils.Compare(childPath, 0, parentPath.Length, parentPath, 0, parentPath.Length) == 0) 918 { 919 uint compareFlags = System.DirectoryServices.ActiveDirectory.Utils.NORM_IGNORENONSPACE | 920 System.DirectoryServices.ActiveDirectory.Utils.NORM_IGNOREKANATYPE | 921 System.DirectoryServices.ActiveDirectory.Utils.NORM_IGNOREWIDTH | 922 System.DirectoryServices.ActiveDirectory.Utils.SORT_STRINGSORT; 923 // work around the ADSI case sensitive 924 if (System.DirectoryServices.ActiveDirectory.Utils.Compare(childPath, 0, parentPath.Length, parentPath, 0, parentPath.Length, compareFlags) != 0) 925 { 926 childPath = parentPath + childPath.Substring(parentPath.Length); 927 } 928 } 929 930 newEntry = newParent.ContainerObject.MoveHere(childPath, newName); 931 } 932 else 933 { 934 newEntry = newParent.ContainerObject.MoveHere(Path, newName); 935 } 936 } 937 catch (COMException e) 938 { 939 throw COMExceptionHelper.CreateFormattedComException(e); 940 } 941 942 if (Bound) 943 System.Runtime.InteropServices.Marshal.ReleaseComObject(_adsObject); // release old handle 944 945 _adsObject = (UnsafeNativeMethods.IAds)newEntry; 946 _path = _adsObject.ADsPath; 947 948 // Reset the options on the ADSI object since there were lost when the new object was created. 949 InitADsObjectOptions(); 950 951 if (!_useCache) 952 CommitChanges(); 953 else 954 RefreshCache(); // in ADSI cache is lost after moving 955 } 956 957 /// <devdoc> 958 /// Loads the property values for this directory entry into the property cache. 959 /// </devdoc> RefreshCache()960 public void RefreshCache() 961 { 962 Bind(); 963 try 964 { 965 _adsObject.GetInfo(); 966 } 967 catch (COMException e) 968 { 969 throw COMExceptionHelper.CreateFormattedComException(e); 970 } 971 972 _cacheFilled = true; 973 // we need to refresh that properties table. 974 _propertyCollection = null; 975 976 // need to refresh the objectSecurity property 977 _objectSecurityInitialized = false; 978 _objectSecurityModified = false; 979 } 980 981 /// <devdoc> 982 /// Loads the values of the specified properties into the property cache. 983 /// </devdoc> RefreshCache(string[] propertyNames)984 public void RefreshCache(string[] propertyNames) 985 { 986 Bind(); 987 988 //Consider there shouldn't be any marshaling issues 989 //by just doing: AdsObject.GetInfoEx(object[]propertyNames, 0); 990 Object[] names = new Object[propertyNames.Length]; 991 for (int i = 0; i < propertyNames.Length; i++) 992 names[i] = propertyNames[i]; 993 try 994 { 995 AdsObject.GetInfoEx(names, 0); 996 } 997 catch (COMException e) 998 { 999 throw COMExceptionHelper.CreateFormattedComException(e); 1000 } 1001 1002 // this is a half-lie, but oh well. Without it, this method is pointless. 1003 _cacheFilled = true; 1004 // we need to partially refresh that properties table. 1005 if (_propertyCollection != null && propertyNames != null) 1006 { 1007 for (int i = 0; i < propertyNames.Length; i++) 1008 { 1009 if (propertyNames[i] != null) 1010 { 1011 string name = propertyNames[i].ToLower(CultureInfo.InvariantCulture); 1012 _propertyCollection.valueTable.Remove(name); 1013 1014 // also need to consider the range retrieval case 1015 string[] results = name.Split(new char[] { ';' }); 1016 if (results.Length != 1) 1017 { 1018 string rangeName = ""; 1019 for (int count = 0; count < results.Length; count++) 1020 { 1021 if (!results[count].StartsWith("range=", StringComparison.Ordinal)) 1022 { 1023 rangeName += results[count]; 1024 rangeName += ";"; 1025 } 1026 } 1027 1028 // remove the last ';' character 1029 rangeName = rangeName.Remove(rangeName.Length - 1, 1); 1030 1031 _propertyCollection.valueTable.Remove(rangeName); 1032 } 1033 1034 // if this is "ntSecurityDescriptor" we should refresh the objectSecurity property 1035 if (String.Compare(propertyNames[i], s_securityDescriptorProperty, StringComparison.OrdinalIgnoreCase) == 0) 1036 { 1037 _objectSecurityInitialized = false; 1038 _objectSecurityModified = false; 1039 } 1040 } 1041 } 1042 } 1043 } 1044 1045 /// <devdoc> 1046 /// Changes the name of this entry. 1047 /// </devdoc> Rename(string newName)1048 public void Rename(string newName) => MoveTo(Parent, newName); 1049 Unbind()1050 private void Unbind() 1051 { 1052 if (_adsObject != null) 1053 System.Runtime.InteropServices.Marshal.ReleaseComObject(_adsObject); 1054 _adsObject = null; 1055 // we need to release that properties table. 1056 _propertyCollection = null; 1057 1058 // need to refresh the objectSecurity property 1059 _objectSecurityInitialized = false; 1060 _objectSecurityModified = false; 1061 } 1062 GetUsername()1063 internal string GetUsername() 1064 { 1065 if (_credentials == null || _userNameIsNull) 1066 return null; 1067 1068 return _credentials.UserName; 1069 } 1070 GetPassword()1071 internal string GetPassword() 1072 { 1073 if (_credentials == null || _passwordIsNull) 1074 return null; 1075 1076 return _credentials.Password; 1077 } 1078 GetObjectSecurityFromCache()1079 private ActiveDirectorySecurity GetObjectSecurityFromCache() 1080 { 1081 try 1082 { 1083 // 1084 // This property is the managed version of the "ntSecurityDescriptor" 1085 // attribute. In order to build an ActiveDirectorySecurity object from it 1086 // we need to get the binary form of the security descriptor. 1087 // If we use IADs::Get to get the IADsSecurityDescriptor interface and then 1088 // convert to raw form, there would be a performance overhead (because of 1089 // sid lookups and reverse lookups during conversion). 1090 // So to get the security descriptor in binary form, we use 1091 // IADsPropertyList::GetPropertyItem 1092 // 1093 1094 // 1095 // GetPropertyItem does not implicitly fill the property cache 1096 // so we need to fill it explicitly (for an existing entry) 1097 // 1098 if (!JustCreated) 1099 { 1100 SecurityMasks securityMasksUsedInRetrieval; 1101 1102 // 1103 // To ensure that we honor the security masks while retrieving 1104 // the security descriptor, we will retrieve the "ntSecurityDescriptor" each time 1105 // while initializing the ObjectSecurity property 1106 // 1107 securityMasksUsedInRetrieval = this.Options.SecurityMasks; 1108 RefreshCache(new string[] { s_securityDescriptorProperty }); 1109 1110 // 1111 // Get the IAdsPropertyList interface 1112 // (Check that the IAdsPropertyList interface is supported) 1113 // 1114 if (!(NativeObject is UnsafeNativeMethods.IAdsPropertyList)) 1115 throw new NotSupportedException(SR.DSPropertyListUnsupported); 1116 1117 UnsafeNativeMethods.IAdsPropertyList list = (UnsafeNativeMethods.IAdsPropertyList)NativeObject; 1118 1119 UnsafeNativeMethods.IAdsPropertyEntry propertyEntry = (UnsafeNativeMethods.IAdsPropertyEntry)list.GetPropertyItem(s_securityDescriptorProperty, (int)AdsType.ADSTYPE_OCTET_STRING); 1120 GC.KeepAlive(this); 1121 1122 // 1123 // Create a new ActiveDirectorySecurity object from the binary form 1124 // of the security descriptor 1125 // 1126 object[] values = (object[])propertyEntry.Values; 1127 1128 // 1129 // This should never happen. It indicates that there is a problem in ADSI's property cache logic. 1130 // 1131 if (values.Length < 1) 1132 { 1133 Debug.Fail("ntSecurityDescriptor property exists in cache but has no values."); 1134 throw new InvalidOperationException(SR.DSSDNoValues); 1135 } 1136 1137 // 1138 // Do not support more than one security descriptor 1139 // 1140 if (values.Length > 1) 1141 { 1142 throw new NotSupportedException(SR.DSMultipleSDNotSupported); 1143 } 1144 1145 UnsafeNativeMethods.IAdsPropertyValue propertyValue = (UnsafeNativeMethods.IAdsPropertyValue)values[0]; 1146 return new ActiveDirectorySecurity((byte[])propertyValue.OctetString, securityMasksUsedInRetrieval); 1147 } 1148 else 1149 { 1150 // 1151 // Newly created directory entry 1152 // 1153 1154 return null; 1155 } 1156 } 1157 catch (System.Runtime.InteropServices.COMException e) 1158 { 1159 if (e.ErrorCode == unchecked((int)0x8000500D)) // property not found exception 1160 return null; 1161 else 1162 throw; 1163 } 1164 } 1165 SetObjectSecurityInCache()1166 private void SetObjectSecurityInCache() 1167 { 1168 if ((_objectSecurity != null) && (_objectSecurityModified || _objectSecurity.IsModified())) 1169 { 1170 UnsafeNativeMethods.IAdsPropertyValue sDValue = (UnsafeNativeMethods.IAdsPropertyValue)new UnsafeNativeMethods.PropertyValue(); 1171 1172 sDValue.ADsType = (int)AdsType.ADSTYPE_OCTET_STRING; 1173 sDValue.OctetString = _objectSecurity.GetSecurityDescriptorBinaryForm(); 1174 1175 UnsafeNativeMethods.IAdsPropertyEntry newSDEntry = (UnsafeNativeMethods.IAdsPropertyEntry)new UnsafeNativeMethods.PropertyEntry(); 1176 1177 newSDEntry.Name = s_securityDescriptorProperty; 1178 newSDEntry.ADsType = (int)AdsType.ADSTYPE_OCTET_STRING; 1179 newSDEntry.ControlCode = (int)AdsPropertyOperation.Update; 1180 newSDEntry.Values = new object[] { sDValue }; 1181 1182 ((UnsafeNativeMethods.IAdsPropertyList)NativeObject).PutPropertyItem(newSDEntry); 1183 } 1184 } 1185 } 1186 } 1187