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.Collections;
6 using System.Diagnostics;
7 using System.Runtime.InteropServices;
8 
9 namespace System.DirectoryServices.ActiveDirectory
10 {
11     public class GlobalCatalog : DomainController
12     {
13         // private variables
14         private ActiveDirectorySchema _schema = null;
15         private bool _disabled = false;
16 
17         #region constructors
GlobalCatalog(DirectoryContext context, string globalCatalogName)18         internal GlobalCatalog(DirectoryContext context, string globalCatalogName)
19             : base(context, globalCatalogName)
20         { }
21 
GlobalCatalog(DirectoryContext context, string globalCatalogName, DirectoryEntryManager directoryEntryMgr)22         internal GlobalCatalog(DirectoryContext context, string globalCatalogName, DirectoryEntryManager directoryEntryMgr)
23             : base(context, globalCatalogName, directoryEntryMgr)
24         { }
25         #endregion constructors
26 
27         #region public methods
28 
GetGlobalCatalog(DirectoryContext context)29         public static GlobalCatalog GetGlobalCatalog(DirectoryContext context)
30         {
31             string gcDnsName = null;
32             bool isGlobalCatalog = false;
33             DirectoryEntryManager directoryEntryMgr = null;
34 
35             // check that the context argument is not null
36             if (context == null)
37                 throw new ArgumentNullException("context");
38 
39             // target should be GC
40             if (context.ContextType != DirectoryContextType.DirectoryServer)
41             {
42                 throw new ArgumentException(SR.TargetShouldBeGC, "context");
43             }
44 
45             // target should be a server
46             if (!(context.isServer()))
47             {
48                 throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFound , context.Name), typeof(GlobalCatalog), context.Name);
49             }
50 
51             //  work with copy of the context
52             context = new DirectoryContext(context);
53 
54             try
55             {
56                 // Get dns name of the dc
57                 // by binding to root dse and getting the "dnsHostName" attribute
58                 // (also check that the "isGlobalCatalogReady" attribute is true)
59                 directoryEntryMgr = new DirectoryEntryManager(context);
60                 DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
61                 if (!Utils.CheckCapability(rootDSE, Capability.ActiveDirectory))
62                 {
63                     throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFound , context.Name), typeof(GlobalCatalog), context.Name);
64                 }
65 
66                 gcDnsName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DnsHostName);
67                 isGlobalCatalog = (bool)Boolean.Parse((string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.IsGlobalCatalogReady));
68                 if (!isGlobalCatalog)
69                 {
70                     throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFound , context.Name), typeof(GlobalCatalog), context.Name);
71                 }
72             }
73             catch (COMException e)
74             {
75                 int errorCode = e.ErrorCode;
76 
77                 if (errorCode == unchecked((int)0x8007203a))
78                 {
79                     throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFound , context.Name), typeof(GlobalCatalog), context.Name);
80                 }
81                 else
82                 {
83                     throw ExceptionHelper.GetExceptionFromCOMException(context, e);
84                 }
85             }
86 
87             return new GlobalCatalog(context, gcDnsName, directoryEntryMgr);
88         }
89 
FindOne(DirectoryContext context)90         public static new GlobalCatalog FindOne(DirectoryContext context)
91         {
92             if (context == null)
93             {
94                 throw new ArgumentNullException("context");
95             }
96 
97             if (context.ContextType != DirectoryContextType.Forest)
98             {
99                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
100             }
101 
102             return FindOneWithCredentialValidation(context, null, 0);
103         }
104 
FindOne(DirectoryContext context, string siteName)105         public static new GlobalCatalog FindOne(DirectoryContext context, string siteName)
106         {
107             if (context == null)
108             {
109                 throw new ArgumentNullException("context");
110             }
111 
112             if (context.ContextType != DirectoryContextType.Forest)
113             {
114                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
115             }
116 
117             if (siteName == null)
118             {
119                 throw new ArgumentNullException("siteName");
120             }
121 
122             return FindOneWithCredentialValidation(context, siteName, 0);
123         }
124 
FindOne(DirectoryContext context, LocatorOptions flag)125         public static new GlobalCatalog FindOne(DirectoryContext context, LocatorOptions flag)
126         {
127             if (context == null)
128             {
129                 throw new ArgumentNullException("context");
130             }
131 
132             if (context.ContextType != DirectoryContextType.Forest)
133             {
134                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
135             }
136 
137             return FindOneWithCredentialValidation(context, null, flag);
138         }
139 
FindOne(DirectoryContext context, string siteName, LocatorOptions flag)140         public static new GlobalCatalog FindOne(DirectoryContext context, string siteName, LocatorOptions flag)
141         {
142             if (context == null)
143             {
144                 throw new ArgumentNullException("context");
145             }
146 
147             if (context.ContextType != DirectoryContextType.Forest)
148             {
149                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
150             }
151 
152             if (siteName == null)
153             {
154                 throw new ArgumentNullException("siteName");
155             }
156 
157             return FindOneWithCredentialValidation(context, siteName, flag);
158         }
159 
FindAll(DirectoryContext context)160         public static new GlobalCatalogCollection FindAll(DirectoryContext context)
161         {
162             if (context == null)
163             {
164                 throw new ArgumentNullException("context");
165             }
166 
167             if (context.ContextType != DirectoryContextType.Forest)
168             {
169                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
170             }
171 
172             //  work with copy of the context
173             context = new DirectoryContext(context);
174 
175             return FindAllInternal(context, null);
176         }
177 
FindAll(DirectoryContext context, string siteName)178         public static new GlobalCatalogCollection FindAll(DirectoryContext context, string siteName)
179         {
180             if (context == null)
181             {
182                 throw new ArgumentNullException("context");
183             }
184 
185             if (context.ContextType != DirectoryContextType.Forest)
186             {
187                 throw new ArgumentException(SR.TargetShouldBeForest, "context");
188             }
189 
190             if (siteName == null)
191             {
192                 throw new ArgumentNullException("siteName");
193             }
194 
195             //  work with copy of the context
196             context = new DirectoryContext(context);
197 
198             return FindAllInternal(context, siteName);
199         }
200 
EnableGlobalCatalog()201         public override GlobalCatalog EnableGlobalCatalog()
202         {
203             CheckIfDisposed();
204             throw new InvalidOperationException(SR.CannotPerformOnGCObject);
205         }
206 
DisableGlobalCatalog()207         public DomainController DisableGlobalCatalog()
208         {
209             CheckIfDisposed();
210             CheckIfDisabled();
211 
212             // bind to the server object
213             DirectoryEntry serverNtdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
214 
215             // reset the NTDSDSA_OPT_IS_GC flag on the "options" property
216             int options = 0;
217 
218             try
219             {
220                 if (serverNtdsaEntry.Properties[PropertyManager.Options].Value != null)
221                 {
222                     options = (int)serverNtdsaEntry.Properties[PropertyManager.Options].Value;
223                 }
224 
225                 serverNtdsaEntry.Properties[PropertyManager.Options].Value = options & (~1);
226                 serverNtdsaEntry.CommitChanges();
227             }
228             catch (COMException e)
229             {
230                 throw ExceptionHelper.GetExceptionFromCOMException(context, e);
231             }
232 
233             // mark as disbaled
234             _disabled = true;
235 
236             // return a domain controller object
237             return new DomainController(context, Name);
238         }
239 
IsGlobalCatalog()240         public override bool IsGlobalCatalog()
241         {
242             CheckIfDisposed();
243             CheckIfDisabled();
244 
245             // since this is a global catalog object, this should always return true
246             return true;
247         }
248 
FindAllProperties()249         public ReadOnlyActiveDirectorySchemaPropertyCollection FindAllProperties()
250         {
251             CheckIfDisposed();
252             CheckIfDisabled();
253 
254             // create an ActiveDirectorySchema object
255             if (_schema == null)
256             {
257                 string schemaNC = null;
258                 try
259                 {
260                     schemaNC = directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.SchemaNamingContext);
261                 }
262                 catch (COMException e)
263                 {
264                     throw ExceptionHelper.GetExceptionFromCOMException(context, e);
265                 }
266                 DirectoryContext schemaContext = Utils.GetNewDirectoryContext(Name, DirectoryContextType.DirectoryServer, context);
267                 _schema = new ActiveDirectorySchema(context, schemaNC);
268             }
269 
270             // return the global catalog replicated properties
271             return _schema.FindAllProperties(PropertyTypes.InGlobalCatalog);
272         }
273 
GetDirectorySearcher()274         public override DirectorySearcher GetDirectorySearcher()
275         {
276             CheckIfDisposed();
277             CheckIfDisabled();
278 
279             return InternalGetDirectorySearcher();
280         }
281 
282         #endregion public methods
283 
284         #region private methods
285 
CheckIfDisabled()286         private void CheckIfDisabled()
287         {
288             if (_disabled)
289             {
290                 throw new InvalidOperationException(SR.GCDisabled);
291             }
292         }
293 
FindOneWithCredentialValidation(DirectoryContext context, string siteName, LocatorOptions flag)294         internal static new GlobalCatalog FindOneWithCredentialValidation(DirectoryContext context, string siteName, LocatorOptions flag)
295         {
296             GlobalCatalog gc;
297             bool retry = false;
298             bool credsValidated = false;
299 
300             //  work with copy of the context
301             context = new DirectoryContext(context);
302 
303             // authenticate against this GC to validate the credentials
304             gc = FindOneInternal(context, context.Name, siteName, flag);
305             try
306             {
307                 ValidateCredential(gc, context);
308                 credsValidated = true;
309             }
310             catch (COMException e)
311             {
312                 if (e.ErrorCode == unchecked((int)0x8007203a))
313                 {
314                     // server is down , so try again with force rediscovery if the flags did not already contain force rediscovery
315                     if ((flag & LocatorOptions.ForceRediscovery) == 0)
316                     {
317                         retry = true;
318                     }
319                     else
320                     {
321                         throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFoundInForest , context.Name), typeof(GlobalCatalog), null);
322                     }
323                 }
324                 else
325                 {
326                     throw ExceptionHelper.GetExceptionFromCOMException(context, e);
327                 }
328             }
329             finally
330             {
331                 if (!credsValidated)
332                 {
333                     gc.Dispose();
334                 }
335             }
336 
337             if (retry)
338             {
339                 credsValidated = false;
340                 gc = FindOneInternal(context, context.Name, siteName, flag | LocatorOptions.ForceRediscovery);
341                 try
342                 {
343                     ValidateCredential(gc, context);
344                     credsValidated = true;
345                 }
346                 catch (COMException e)
347                 {
348                     if (e.ErrorCode == unchecked((int)0x8007203a))
349                     {
350                         // server is down
351                         throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFoundInForest , context.Name), typeof(GlobalCatalog), null);
352                     }
353                     else
354                     {
355                         throw ExceptionHelper.GetExceptionFromCOMException(context, e);
356                     }
357                 }
358                 finally
359                 {
360                     if (!credsValidated)
361                     {
362                         gc.Dispose();
363                     }
364                 }
365             }
366 
367             return gc;
368         }
369 
FindOneInternal(DirectoryContext context, string forestName, string siteName, LocatorOptions flag)370         internal static new GlobalCatalog FindOneInternal(DirectoryContext context, string forestName, string siteName, LocatorOptions flag)
371         {
372             DomainControllerInfo domainControllerInfo;
373             int errorCode = 0;
374 
375             if (siteName != null && siteName.Length == 0)
376             {
377                 throw new ArgumentException(SR.EmptyStringParameter, "siteName");
378             }
379 
380             // check that the flags passed have only the valid bits set
381             if (((long)flag & (~((long)LocatorOptions.AvoidSelf | (long)LocatorOptions.ForceRediscovery | (long)LocatorOptions.KdcRequired | (long)LocatorOptions.TimeServerRequired | (long)LocatorOptions.WriteableRequired))) != 0)
382             {
383                 throw new ArgumentException(SR.InvalidFlags, "flag");
384             }
385 
386             if (forestName == null)
387             {
388                 // get the dns name of the logged on forest
389                 DomainControllerInfo tempDomainControllerInfo;
390                 int error = Locator.DsGetDcNameWrapper(null, DirectoryContext.GetLoggedOnDomain(), null, (long)PrivateLocatorFlags.DirectoryServicesRequired, out tempDomainControllerInfo);
391 
392                 if (error == NativeMethods.ERROR_NO_SUCH_DOMAIN)
393                 {
394                     // throw not found exception
395                     throw new ActiveDirectoryObjectNotFoundException(SR.ContextNotAssociatedWithDomain, typeof(GlobalCatalog), null);
396                 }
397                 else if (error != 0)
398                 {
399                     throw ExceptionHelper.GetExceptionFromErrorCode(errorCode);
400                 }
401 
402                 Debug.Assert(tempDomainControllerInfo.DnsForestName != null);
403                 forestName = tempDomainControllerInfo.DnsForestName;
404             }
405 
406             // call DsGetDcName
407             errorCode = Locator.DsGetDcNameWrapper(null, forestName, siteName, (long)flag | (long)(PrivateLocatorFlags.GCRequired | PrivateLocatorFlags.DirectoryServicesRequired), out domainControllerInfo);
408 
409             if (errorCode == NativeMethods.ERROR_NO_SUCH_DOMAIN)
410             {
411                 throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.GCNotFoundInForest , forestName), typeof(GlobalCatalog), null);
412             }
413             // this can only occur when flag is being explicitly passed (since the flags that we pass internally are valid)
414             if (errorCode == NativeMethods.ERROR_INVALID_FLAGS)
415             {
416                 throw new ArgumentException(SR.InvalidFlags, "flag");
417             }
418             else if (errorCode != 0)
419             {
420                 throw ExceptionHelper.GetExceptionFromErrorCode(errorCode);
421             }
422 
423             // create a GlobalCatalog object
424             // the name is returned in the form "\\servername", so skip the "\\"
425             Debug.Assert(domainControllerInfo.DomainControllerName.Length > 2);
426             string globalCatalogName = domainControllerInfo.DomainControllerName.Substring(2);
427 
428             // create a new context object for the global catalog
429             DirectoryContext gcContext = Utils.GetNewDirectoryContext(globalCatalogName, DirectoryContextType.DirectoryServer, context);
430 
431             return new GlobalCatalog(gcContext, globalCatalogName);
432         }
433 
FindAllInternal(DirectoryContext context, string siteName)434         internal static GlobalCatalogCollection FindAllInternal(DirectoryContext context, string siteName)
435         {
436             ArrayList gcList = new ArrayList();
437 
438             if (siteName != null && siteName.Length == 0)
439             {
440                 throw new ArgumentException(SR.EmptyStringParameter, "siteName");
441             }
442 
443             foreach (string gcName in Utils.GetReplicaList(context, null /* not specific to any partition */, siteName, false /* isDefaultNC */, false /* isADAM */, true /* mustBeGC */))
444             {
445                 DirectoryContext gcContext = Utils.GetNewDirectoryContext(gcName, DirectoryContextType.DirectoryServer, context);
446                 gcList.Add(new GlobalCatalog(gcContext, gcName));
447             }
448 
449             return new GlobalCatalogCollection(gcList);
450         }
451 
InternalGetDirectorySearcher()452         private DirectorySearcher InternalGetDirectorySearcher()
453         {
454             DirectoryEntry de = new DirectoryEntry("GC://" + Name);
455 
456             de.AuthenticationType = Utils.DefaultAuthType | AuthenticationTypes.ServerBind;
457 
458             de.Username = context.UserName;
459             de.Password = context.Password;
460 
461             return new DirectorySearcher(de);
462         }
463 
464         #endregion
465     }
466 }
467