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