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