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.DirectoryServices;
7 using System.Collections.Generic;
8 using System.Collections;
9 using System.Diagnostics;
10 using System.Security.Principal;
11 
12 namespace System.DirectoryServices.AccountManagement
13 {
14     internal class ADDNLinkedAttrSet : BookmarkableResultSet
15     {
16         // This class can be used to either enumerate the members of a group, or the groups
17         // to which a principal belongs.  If being used to enumerate the members of a group:
18         //      * groupDN --- the DN of the group we're enumerating
19         //      * members --- array of enumerators containing the DNs of the members of the group we're enumerating (the "member" attribute)
20         //      * primaryGroupDN --- should be null
21         //      * recursive --- whether or not to recursively enumerate group membership
22         //
23         // If being used to enumerate the groups to which a principal belongs:
24         //      * groupDN --- the DN of the principal (i.e., the user)
25         //      * members --- the DNs of the groups to which that principal belongs (e.g, the "memberOf" attribute)
26         //      * primaryGroupDN --- the DN of the principal's primary group (constructed from the "primaryGroupID" attribute)
27         //      * recursive --- should be false
28         //
29         // Note that the variables in this class are generally named in accord with the "enumerating the members
30         // of a group" case.
31         //
32         // It is assumed that recursive enumeration will only be performed for the "enumerating the members of a group"
33         // case, not the "groups to which a principal belongs" case, thus, this.recursive == true implies the former
34         // (but this.recursive == false could imply either case).
35 
ADDNLinkedAttrSet( string groupDN, IEnumerable[] members, string primaryGroupDN, DirectorySearcher primaryGroupMembersSearcher, bool recursive, ADStoreCtx storeCtx)36         internal ADDNLinkedAttrSet(
37                             string groupDN,
38                             IEnumerable[] members,
39                             string primaryGroupDN,
40                             DirectorySearcher primaryGroupMembersSearcher,
41                             bool recursive,
42                             ADStoreCtx storeCtx)
43         {
44             GlobalDebug.WriteLineIf(GlobalDebug.Info,
45                                     "ADDNLinkedAttrSet",
46                                     "ADDNLinkedAttrSet: groupDN={0}, primaryGroupDN={1}, recursive={2}, PG queryFilter={3}, PG queryBase={4}",
47                                     groupDN,
48                                     (primaryGroupDN != null ? primaryGroupDN : "NULL"),
49                                     recursive,
50                                     (primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.Filter : "NULL"),
51                                     (primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.SearchRoot.Path : "NULL"));
52 
53             _groupsVisited.Add(groupDN);    // so we don't revisit it
54             _recursive = recursive;
55             _storeCtx = storeCtx;
56             _originalStoreCtx = storeCtx;
57 
58             if (null != members)
59             {
60                 foreach (IEnumerable enumerator in members)
61                 {
62                     _membersQueue.Enqueue(enumerator);
63                     _originalMembers.Enqueue(enumerator);
64                 }
65             }
66 
67             _members = null;
68 
69             _currentMembersSearcher = null;
70             _primaryGroupDN = primaryGroupDN;
71             if (primaryGroupDN == null)
72                 _returnedPrimaryGroup = true;    // so we don't bother trying to return the primary group
73 
74             _primaryGroupMembersSearcher = primaryGroupMembersSearcher;
75 
76             _expansionMode = ExpansionMode.Enum;
77             _originalExpansionMode = _expansionMode;
78         }
79 
ADDNLinkedAttrSet( string groupDN, DirectorySearcher[] membersSearcher, string primaryGroupDN, DirectorySearcher primaryGroupMembersSearcher, bool recursive, ADStoreCtx storeCtx)80         internal ADDNLinkedAttrSet(
81                             string groupDN,
82                             DirectorySearcher[] membersSearcher,
83                             string primaryGroupDN,
84                             DirectorySearcher primaryGroupMembersSearcher,
85                             bool recursive,
86                             ADStoreCtx storeCtx)
87 
88         {
89             GlobalDebug.WriteLineIf(GlobalDebug.Info,
90                                     "ADDNLinkedAttrSet",
91                                     "ADDNLinkedAttrSet: groupDN={0}, primaryGroupDN={1}, recursive={2}, M queryFilter={3}, M queryBase={4}, PG queryFilter={5}, PG queryBase={6}",
92                                     groupDN,
93                                     (primaryGroupDN != null ? primaryGroupDN : "NULL"),
94                                     recursive,
95                                     (membersSearcher != null ? membersSearcher[0].Filter : "NULL"),
96                                     (membersSearcher != null ? membersSearcher[0].SearchRoot.Path : "NULL"),
97                                     (primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.Filter : "NULL"),
98                                     (primaryGroupMembersSearcher != null ? primaryGroupMembersSearcher.SearchRoot.Path : "NULL"));
99 
100             _groupsVisited.Add(groupDN);    // so we don't revisit it
101             _recursive = recursive;
102             _storeCtx = storeCtx;
103             _originalStoreCtx = storeCtx;
104 
105             _members = null;
106             _originalMembers = null;
107             _membersEnum = null;
108 
109             _primaryGroupDN = primaryGroupDN;
110             if (primaryGroupDN == null)
111                 _returnedPrimaryGroup = true;    // so we don't bother trying to return the primary group
112 
113             if (null != membersSearcher)
114             {
115                 foreach (DirectorySearcher ds in membersSearcher)
116                 {
117                     _memberSearchersQueue.Enqueue(ds);
118                     _memberSearchersQueueOriginal.Enqueue(ds);
119                 }
120             }
121 
122             _currentMembersSearcher = null;
123 
124             _primaryGroupMembersSearcher = primaryGroupMembersSearcher;
125 
126             _expansionMode = ExpansionMode.ASQ;
127             _originalExpansionMode = _expansionMode;
128         }
129 
130         // Return the principal we're positioned at as a Principal object.
131         // Need to use our StoreCtx's GetAsPrincipal to convert the native object to a Principal
132         override internal object CurrentAsPrincipal
133         {
134             get
135             {
136                 if (this.current != null)
137                 {
138                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "CurrentAsPrincipal: using current");
139                     if (this.current is DirectoryEntry)
140                         return ADUtils.DirectoryEntryAsPrincipal((DirectoryEntry)this.current, _storeCtx);
141                     else
142                     {
143                         return ADUtils.SearchResultAsPrincipal((SearchResult)this.current, _storeCtx, null);
144                     }
145                 }
146                 else
147                 {
148                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "CurrentAsPrincipal: using currentForeignPrincipal");
149                     Debug.Assert(_currentForeignPrincipal != null);
150                     return _currentForeignPrincipal;
151                 }
152             }
153         }
154 
155         // Advance the enumerator to the next principal in the result set, pulling in additional pages
156         // of results (or ranges of attribute values) as needed.
157         // Returns true if successful, false if no more results to return.
MoveNext()158         override internal bool MoveNext()
159         {
160             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNext");
161 
162             _atBeginning = false;
163 
164             bool needToRetry;
165             bool f = false;
166 
167             do
168             {
169                 needToRetry = false;
170                 // reset our found state.  If we are restarting the loop we don't have a current principal yet.
171                 f = false;
172 
173                 if (!_returnedPrimaryGroup)
174                 {
175                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying PrimaryGroup DN");
176                     f = MoveNextPrimaryGroupDN();
177                 }
178 
179                 if (!f)
180                 {
181                     if (_expansionMode == ExpansionMode.ASQ)
182                     {
183                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying member searcher");
184                         f = MoveNextMemberSearcher();
185                     }
186                     else
187                     {
188                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying member enum");
189                         f = MoveNextMemberEnum();
190                     }
191                 }
192 
193                 if (!f)
194                 {
195                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying foreign");
196                     f = MoveNextForeign(ref needToRetry);
197                 }
198 
199                 if (!f)
200                 {
201                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNext: trying primary group search");
202                     f = MoveNextQueryPrimaryGroupMember();
203                 }
204             }
205             while (needToRetry);
206 
207             return f;
208         }
209 
MoveNextPrimaryGroupDN()210         private bool MoveNextPrimaryGroupDN()
211         {
212             // Do the special primary group ID processing if we haven't yet returned the primary group.
213             Debug.Assert(_primaryGroupDN != null);
214 
215             this.current = SDSUtils.BuildDirectoryEntry(
216                                         BuildPathFromDN(_primaryGroupDN),
217                                         _storeCtx.Credentials,
218                                         _storeCtx.AuthTypes);
219 
220             _storeCtx.InitializeNewDirectoryOptions((DirectoryEntry)this.current);
221 
222             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: returning primary group {0}", ((DirectoryEntry)this.current).Path);
223 
224             _currentForeignDE = null;
225             _currentForeignPrincipal = null;
226 
227             _returnedPrimaryGroup = true;
228             return true;
229         }
230 
GetNextSearchResult()231         private bool GetNextSearchResult()
232         {
233             bool memberFound = false;
234 
235             do
236             {
237                 if (_currentMembersSearcher == null)
238                 {
239                     Debug.Assert(_memberSearchersQueue != null);
240 
241                     if (_memberSearchersQueue.Count == 0)
242                     {
243                         // We are out of searchers in the queue.
244                         return false;
245                     }
246                     else
247                     {
248                         // Remove the next searcher from the queue and place it in the current search variable.
249                         _currentMembersSearcher = _memberSearchersQueue.Dequeue();
250                         _memberSearchResults = _currentMembersSearcher.FindAll();
251                         Debug.Assert(_memberSearchResults != null);
252                         _memberSearchResultsEnumerator = _memberSearchResults.GetEnumerator();
253                     }
254                 }
255 
256                 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: have a searcher");
257 
258                 memberFound = _memberSearchResultsEnumerator.MoveNext();
259 
260                 // The search is complete.
261                 // Dipose the searcher and search results.
262                 if (!memberFound)
263                 {
264                     _currentMembersSearcher.Dispose();
265                     _currentMembersSearcher = null;
266                     _memberSearchResults.Dispose();
267                     _memberSearchResults = null;
268                 }
269             } while (!memberFound);
270 
271             return memberFound;
272         }
273 
MoveNextMemberSearcher()274         private bool MoveNextMemberSearcher()
275         {
276             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextMemberSearcher");
277 
278             bool needToRetry = false;
279             bool f = false;
280 
281             do
282             {
283                 f = GetNextSearchResult();
284                 needToRetry = false;
285 
286                 if (f)
287                 {
288                     SearchResult currentSR = (SearchResult)_memberSearchResultsEnumerator.Current;
289 
290                     // Got a member from this group (or, got a group of which we're a member).
291                     // Create a DirectoryEntry for it.
292                     string memberDN = (string)currentSR.Properties["distinguishedName"][0];
293 
294                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: got a value from the enumerator: {0}", memberDN);
295 
296                     // Make sure the member is a principal
297                     if ((!ADUtils.IsOfObjectClass(currentSR, "group")) &&
298                          (!ADUtils.IsOfObjectClass(currentSR, "user")) &&     // includes computer as well
299                          (!ADUtils.IsOfObjectClass(currentSR, "foreignSecurityPrincipal")))
300                     {
301                         // We found a member, but it's not a principal type.  Skip it.
302                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: not a principal, skipping");
303                         needToRetry = true;
304                     }
305                     // If we're processing recursively, and the member is a group, we DON'T return it,
306                     // but rather treat it as something to recursively visit later
307                     // (unless we've already visited the group previously)
308                     else if (_recursive && ADUtils.IsOfObjectClass(currentSR, "group"))
309                     {
310                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: adding to groupsToVisit");
311 
312                         if (!_groupsVisited.Contains(memberDN) && !_groupsToVisit.Contains(memberDN))
313                             _groupsToVisit.Add(memberDN);
314 
315                         // and go on to the next member....
316                         needToRetry = true;
317                     }
318                     else if (_recursive && ADUtils.IsOfObjectClass(currentSR, "foreignSecurityPrincipal"))
319                     {
320                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: foreign principal, adding to foreignMembers");
321 
322                         // If we haven't seen this FPO yet then add it to the seen user database.
323                         if (!_usersVisited.ContainsKey(currentSR.Properties["distinguishedName"][0].ToString()))
324                         {
325                             // The FPO might represent a group, in which case we should recursively enumerate its
326                             // membership.  So save it off for later processing.
327                             _foreignMembersCurrentGroup.Add(currentSR.GetDirectoryEntry());
328                             _usersVisited.Add(currentSR.Properties["distinguishedName"][0].ToString(), true);
329                         }
330 
331                         // and go on to the next member....
332                         needToRetry = true;
333                     }
334                     else
335                     {
336                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: using as current");
337 
338                         // Check to see if we have already seen this user during the enumeration
339                         // If so then move on to the next user.  If not then return it as current.
340                         if (!_usersVisited.ContainsKey(currentSR.Properties["distinguishedName"][0].ToString()))
341                         {
342                             this.current = currentSR;
343                             _currentForeignDE = null;
344                             _currentForeignPrincipal = null;
345                             _usersVisited.Add(currentSR.Properties["distinguishedName"][0].ToString(), true);
346                         }
347                         else
348                         {
349                             needToRetry = true;
350                         }
351                     }
352                 }
353                 else
354                 {
355                     // We reached the end of this group's membership.  If we're not processing recursively,
356                     // we're done.  Otherwise, go on to the next group to visit.
357                     // First create a DE that points to the group we want to expand,  Using that as a search root run
358                     // an ASQ search against  member and start enumerting those results.
359                     if (_recursive)
360                     {
361                         GlobalDebug.WriteLineIf(GlobalDebug.Info,
362                                                 "ADDNLinkedAttrSet",
363                                                 "MoveNextMemberSearcher: recursive processing, groupsToVisit={0}",
364                                                 _groupsToVisit.Count);
365 
366                         if (_groupsToVisit.Count > 0)
367                         {
368                             // Pull off the next group to visit
369                             string groupDN = _groupsToVisit[0];
370                             _groupsToVisit.RemoveAt(0);
371                             _groupsVisited.Add(groupDN);
372 
373                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberSearcher: recursively processing {0}", groupDN);
374 
375                             // get the membership of this new group
376                             DirectoryEntry groupDE = SDSUtils.BuildDirectoryEntry(BuildPathFromDN(groupDN), _storeCtx.Credentials, _storeCtx.AuthTypes);
377 
378                             _storeCtx.InitializeNewDirectoryOptions(groupDE);
379 
380                             // Queue up a searcher for the new group expansion.
381                             DirectorySearcher ds = SDSUtils.ConstructSearcher(groupDE);
382                             ds.Filter = "(objectClass=*)";
383                             ds.SearchScope = SearchScope.Base;
384                             ds.AttributeScopeQuery = "member";
385                             ds.CacheResults = false;
386 
387                             _memberSearchersQueue.Enqueue(ds);
388 
389                             // and go on to the first member of this new group.
390                             needToRetry = true;
391                         }
392                     }
393                 }
394             }
395             while (needToRetry);
396 
397             return f;
398         }
399 
GetNextEnum()400         private bool GetNextEnum()
401         {
402             bool memberFound = false;
403 
404             do
405             {
406                 if (null == _members)
407                 {
408                     if (_membersQueue.Count == 0)
409                     {
410                         return false;
411                     }
412 
413                     _members = _membersQueue.Dequeue();
414                     _membersEnum = _members.GetEnumerator();
415                 }
416 
417                 memberFound = _membersEnum.MoveNext();
418 
419                 if (!memberFound)
420                 {
421                     IDisposable disposableMembers = _members as IDisposable;
422                     if (disposableMembers != null)
423                     {
424                         disposableMembers.Dispose();
425                     }
426                     IDisposable disposableMembersEnum = _membersEnum as IDisposable;
427                     if (disposableMembersEnum != null)
428                     {
429                         disposableMembersEnum.Dispose();
430                     }
431                     _members = null;
432                     _membersEnum = null;
433                 }
434             } while (!memberFound);
435 
436             return memberFound;
437         }
438 
MoveNextMemberEnum()439         private bool MoveNextMemberEnum()
440         {
441             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextMemberEnum");
442 
443             bool needToRetry = false;
444             bool disposeMemberDE = false;
445             bool f;
446 
447             do
448             {
449                 f = GetNextEnum();
450                 needToRetry = false;
451                 disposeMemberDE = false;
452 
453                 if (f)
454                 {
455                     DirectoryEntry memberDE = null;
456                     try
457                     {
458                         // Got a member from this group (or, got a group of which we're a member).
459                         // Create a DirectoryEntry for it.
460                         string memberDN = (string)_membersEnum.Current;
461 
462                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: got a value from the enumerator: {0}", memberDN);
463 
464                         memberDE = SDSUtils.BuildDirectoryEntry(
465                                                         BuildPathFromDN(memberDN),
466                                                         _storeCtx.Credentials,
467                                                         _storeCtx.AuthTypes);
468 
469                         _storeCtx.InitializeNewDirectoryOptions(memberDE);
470 
471                         _storeCtx.LoadDirectoryEntryAttributes(memberDE);
472 
473                         // Make sure the member is a principal
474                         if ((!ADUtils.IsOfObjectClass(memberDE, "group")) &&
475                              (!ADUtils.IsOfObjectClass(memberDE, "user")) &&     // includes computer as well
476                              (!ADUtils.IsOfObjectClass(memberDE, "foreignSecurityPrincipal")))
477                         {
478                             // We found a member, but it's not a principal type.  Skip it.
479                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: not a principal, skipping");
480                             needToRetry = true;
481                             disposeMemberDE = true; //Since member is not principal we don't return it. So mark it for dispose.
482                         }
483                         // If we're processing recursively, and the member is a group, we DON'T return it,
484                         // but rather treat it as something to recursively visit later
485                         // (unless we've already visited the group previously)
486                         else if (_recursive && ADUtils.IsOfObjectClass(memberDE, "group"))
487                         {
488                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: adding to groupsToVisit");
489 
490                             if (!_groupsVisited.Contains(memberDN) && !_groupsToVisit.Contains(memberDN))
491                                 _groupsToVisit.Add(memberDN);
492 
493                             // and go on to the next member....
494                             needToRetry = true;
495                             disposeMemberDE = true; //Since recursive is set to true, we do not return groups. So mark it for dispose.
496                         }
497                         else if (_recursive && ADUtils.IsOfObjectClass(memberDE, "foreignSecurityPrincipal"))
498                         {
499                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: foreign principal, adding to foreignMembers");
500 
501                             // If we haven't seen this FPO yet then add it to the seen user database.
502                             if (!_usersVisited.ContainsKey(memberDE.Properties["distinguishedName"][0].ToString()))
503                             {
504                                 // The FPO might represent a group, in which case we should recursively enumerate its
505                                 // membership.  So save it off for later processing.
506                                 _foreignMembersCurrentGroup.Add(memberDE);
507                                 _usersVisited.Add(memberDE.Properties["distinguishedName"][0].ToString(), true);
508                                 disposeMemberDE = false; //We store the FPO DirectoryEntry objects for further processing. So do NOT dispose it.
509                             }
510 
511                             // and go on to the next member....
512                             needToRetry = true;
513                         }
514                         else
515                         {
516                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: using as current");
517 
518                             // Check to see if we have already seen this user during the enumeration
519                             // If so then move on to the next user.  If not then return it as current.
520                             if (!_usersVisited.ContainsKey(memberDE.Properties["distinguishedName"][0].ToString()))
521                             {
522                                 this.current = memberDE;
523                                 _currentForeignDE = null;
524                                 _currentForeignPrincipal = null;
525                                 _usersVisited.Add(memberDE.Properties["distinguishedName"][0].ToString(), true);
526                                 disposeMemberDE = false; //memberDE will be set in the Principal object we return. So do NOT dispose it.
527                             }
528                             else
529                             {
530                                 needToRetry = true;
531                             }
532                         }
533                     }
534                     finally
535                     {
536                         if (disposeMemberDE && memberDE != null)
537                         {
538                             //This means the constructed member is not used in the new principal
539                             memberDE.Dispose();
540                         }
541                     }
542                 }
543                 else
544                 {
545                     // We reached the end of this group's membership.  If we're not processing recursively,
546                     // we're done.  Otherwise, go on to the next group to visit.
547                     if (_recursive)
548                     {
549                         GlobalDebug.WriteLineIf(GlobalDebug.Info,
550                                                 "ADDNLinkedAttrSet",
551                                                 "MoveNextLocal: recursive processing, groupsToVisit={0}",
552                                                 _groupsToVisit.Count);
553 
554                         if (_groupsToVisit.Count > 0)
555                         {
556                             // Pull off the next group to visit
557                             string groupDN = _groupsToVisit[0];
558                             _groupsToVisit.RemoveAt(0);
559                             _groupsVisited.Add(groupDN);
560 
561                             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextMemberEnum: recursively processing {0}", groupDN);
562 
563                             // get the membership of this new group
564                             DirectoryEntry groupDE = SDSUtils.BuildDirectoryEntry(
565                                                                     BuildPathFromDN(groupDN),
566                                                     _storeCtx.Credentials,
567                                                     _storeCtx.AuthTypes);
568 
569                             _storeCtx.InitializeNewDirectoryOptions(groupDE);
570 
571                             // set up for the next round of enumeration
572                             //Here a new DirectoryEntry object is created and passed
573                             //to RangeRetriever object. Hence, configure
574                             //RangeRetriever to dispose the DirEntry on its dispose.
575                             _membersQueue.Enqueue(new RangeRetriever(groupDE, "member", true));
576 
577                             // and go on to the first member of this new group....
578                             needToRetry = true;
579                         }
580                     }
581                 }
582             }
583             while (needToRetry);
584 
585             return f;
586         }
587 
TranslateForeignMembers()588         private void TranslateForeignMembers()
589         {
590             GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "TranslateForeignMembers: Translating foreign members");
591 
592             List<Byte[]> sidList = new List<Byte[]>(_foreignMembersCurrentGroup.Count);
593 
594             // Foreach foreign principal retrive the sid.
595             // If the SID is for a fake object we have to track it separately.  If we were attempt to translate it
596             // it would fail and not be returned and we would lose it.
597             // Once we have a list of sids then translate them against the target store in one call.
598             foreach (DirectoryEntry de in _foreignMembersCurrentGroup)
599             {
600                 // Get the SID of the foreign principal
601                 if (de.Properties["objectSid"].Count == 0)
602                 {
603                     throw new PrincipalOperationException(SR.ADStoreCtxCantRetrieveObjectSidForCrossStore);
604                 }
605 
606                 Byte[] sid = (Byte[])de.Properties["objectSid"].Value;
607 
608                 // What type of SID is it?
609                 SidType sidType = Utils.ClassifySID(sid);
610 
611                 if (sidType == SidType.FakeObject)
612                 {
613                     //Add the foreign member DirectoryEntry to fakePrincipalMembers list for further translation
614                     //This de will be disposed after completing the translation by another code block.
615                     _fakePrincipalMembers.Add(de);
616 
617                     // It's a FPO for something like NT AUTHORITY\NETWORK SERVICE.
618                     // There's no real store object corresponding to this FPO, so
619                     // fake a Principal.
620                     GlobalDebug.WriteLineIf(GlobalDebug.Info,
621                                             "ADDNLinkedAttrSet",
622                                             "TranslateForeignMembers: fake principal, SID={0}",
623                                             Utils.ByteArrayToString(sid));
624                 }
625                 else
626                 {
627                     GlobalDebug.WriteLineIf(GlobalDebug.Info,
628                                             "ADDNLinkedAttrSet",
629                                             "TranslateForeignMembers: standard principal, SID={0}",
630                                             Utils.ByteArrayToString(sid));
631 
632                     sidList.Add(sid);
633                     //We do NOT need the Foreign member DirectoryEntry object once it has been translated and added to sidList.
634                     //So disposing it off now
635                     de.Dispose();
636                 }
637             }
638 
639             // This call will perform a bulk sid translate to the name + issuer domain.
640             _foreignMembersToReturn = new SidList(sidList, _storeCtx.DnsHostName, _storeCtx.Credentials);
641 
642             // We have translated the sids so clear the group now.
643             _foreignMembersCurrentGroup.Clear();
644         }
645 
MoveNextForeign(ref bool outerNeedToRetry)646         private bool MoveNextForeign(ref bool outerNeedToRetry)
647         {
648             outerNeedToRetry = false;
649             bool needToRetry;
650             Principal foreignPrincipal;
651             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Entering MoveNextForeign");
652 
653             do
654             {
655                 needToRetry = false;
656 
657                 if (_foreignMembersCurrentGroup.Count > 0)
658                 {
659                     TranslateForeignMembers();
660                 }
661 
662                 if (_fakePrincipalMembers.Count > 0)
663                 {
664                     foreignPrincipal = _storeCtx.ConstructFakePrincipalFromSID((Byte[])_fakePrincipalMembers[0].Properties["objectSid"].Value);
665                     _fakePrincipalMembers[0].Dispose();
666                     _fakePrincipalMembers.RemoveAt(0);
667                 }
668                 else if ((_foreignMembersToReturn != null) && (_foreignMembersToReturn.Length > 0))
669                 {
670                     StoreCtx foreignStoreCtx;
671 
672                     SidListEntry foreignSid = _foreignMembersToReturn[0];
673 
674                     // sidIssuerName is null only if SID was not resolved
675                     // return a unknown principal back
676                     if (null == foreignSid.sidIssuerName)
677                     {
678                         // create and return the unknown principal if it is not yet present in usersVisited
679                         if (!_usersVisited.ContainsKey(foreignSid.name))
680                         {
681                             byte[] sid = Utils.ConvertNativeSidToByteArray(foreignSid.pSid);
682                             UnknownPrincipal unknownPrincipal = UnknownPrincipal.CreateUnknownPrincipal(_storeCtx.OwningContext, sid, foreignSid.name);
683                             _usersVisited.Add(foreignSid.name, true);
684                             this.current = null;
685                             _currentForeignDE = null;
686                             _currentForeignPrincipal = unknownPrincipal;
687                             // remove the current member
688                             _foreignMembersToReturn.RemoveAt(0);
689                             return true;
690                         }
691 
692                         // remove the current member
693                         _foreignMembersToReturn.RemoveAt(0);
694 
695                         needToRetry = true;
696                         continue;
697                     }
698 
699                     SidType sidType = Utils.ClassifySID(foreignSid.pSid);
700 
701                     if (sidType == SidType.RealObjectFakeDomain)
702                     {
703                         // This is a BUILTIN object.  It's a real object on the store we're connected to, but LookupSid
704                         // will tell us it's a member of the BUILTIN domain.  Resolve it as a principal on our store.
705                         GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "MoveNextForeign: builtin principal");
706                         foreignStoreCtx = _storeCtx;
707                     }
708                     else
709                     {
710                         ContextOptions remoteOptions = DefaultContextOptions.ADDefaultContextOption;
711 
712 #if USE_CTX_CACHE
713                         PrincipalContext remoteCtx = SDSCache.Domain.GetContext(foreignSid.sidIssuerName, _storeCtx.Credentials, remoteOptions);
714 #else
715                         PrincipalContext remoteCtx = new PrincipalContext(
716                                         ContextType.Domain,
717                                         foreignSid.sidIssuerName,
718                                         null,
719                                         (this.storeCtx.Credentials != null ? this.storeCtx.Credentials.UserName : null),
720                                         (this.storeCtx.Credentials != null ? storeCtx.storeCtx.Credentials.Password : null),
721                                         remoteOptions);
722 
723 #endif
724                         foreignStoreCtx = remoteCtx.QueryCtx;
725                     }
726 
727                     foreignPrincipal = foreignStoreCtx.FindPrincipalByIdentRef(
728                                                      typeof(Principal),
729                                                      UrnScheme.SidScheme,
730                                                      (new SecurityIdentifier(Utils.ConvertNativeSidToByteArray(_foreignMembersToReturn[0].pSid), 0)).ToString(),
731                                                      DateTime.UtcNow);
732 
733                     if (null == foreignPrincipal)
734                     {
735                         GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADDNLinkedAttrSet", "MoveNextForeign: no matching principal");
736                         throw new PrincipalOperationException(SR.ADStoreCtxFailedFindCrossStoreTarget);
737                     }
738 
739                     _foreignMembersToReturn.RemoveAt(0);
740                 }
741                 else
742                 {
743                     // We don't have any more foreign principals to return so start with the foreign groups
744                     if (_foreignGroups.Count > 0)
745                     {
746                         outerNeedToRetry = true;
747 
748                         // Determine the domainFunctionalityMode of the foreign domain.  If they are W2k or not a global group then we can't use ASQ.
749                         if (_foreignGroups[0].Context.ServerInformation.OsVersion == DomainControllerMode.Win2k ||
750                             _foreignGroups[0].GroupScope != GroupScope.Global)
751                         {
752                             _expansionMode = ExpansionMode.Enum;
753                             return ExpandForeignGroupEnumerator();
754                         }
755                         else
756                         {
757                             _expansionMode = ExpansionMode.ASQ;
758                             return ExpandForeignGroupSearcher();
759                         }
760                     }
761                     else
762                     {
763                         // We are done with foreign principals and groups..
764                         return false;
765                     }
766                 }
767 
768                 if (foreignPrincipal is GroupPrincipal)
769                 {
770                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextForeign: foreign member is a group");
771 
772                     // A group, need to recursively expand it (unless it's a fake group,
773                     // in which case it is by definition empty and so contains nothing to expand, or unless
774                     // we've already or will visit it).
775                     // Postpone to later.
776                     if (!foreignPrincipal.fakePrincipal)
777                     {
778                         string groupDN = (string)((DirectoryEntry)foreignPrincipal.UnderlyingObject).Properties["distinguishedName"].Value;
779 
780                         GlobalDebug.WriteLineIf(GlobalDebug.Info,
781                                                 "ADDNLinkedAttrSet",
782                                                 "MoveNextForeign: not a fake group, adding {0} to foreignGroups",
783                                                 groupDN);
784 
785                         if (!_groupsVisited.Contains(groupDN) && !_groupsToVisit.Contains(groupDN))
786                         {
787                             _foreignGroups.Add((GroupPrincipal)foreignPrincipal);
788                         }
789                         else
790                         {
791                             foreignPrincipal.Dispose();
792                         }
793                     }
794 
795                     needToRetry = true;
796                     continue;
797                 }
798                 else
799                 {
800                     // Not a group, nothing to recursively expand, so just return it.
801                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextForeign: using as currentForeignDE/currentForeignPrincipal");
802 
803                     DirectoryEntry foreignDE = (DirectoryEntry)foreignPrincipal.GetUnderlyingObject();
804 
805                     _storeCtx.LoadDirectoryEntryAttributes(foreignDE);
806 
807                     if (!_usersVisited.ContainsKey(foreignDE.Properties["distinguishedName"][0].ToString()))
808                     {
809                         _usersVisited.Add(foreignDE.Properties["distinguishedName"][0].ToString(), true);
810                         this.current = null;
811                         _currentForeignDE = null;
812                         _currentForeignPrincipal = foreignPrincipal;
813                         return true;
814                     }
815                     else
816                     {
817                         foreignPrincipal.Dispose();
818                     }
819 
820                     needToRetry = true;
821                     continue;
822                 }
823             }
824             while (needToRetry);
825 
826             return false;
827         }
828 
ExpandForeignGroupEnumerator()829         private bool ExpandForeignGroupEnumerator()
830         {
831             Debug.Assert(_recursive == true);
832             GlobalDebug.WriteLineIf(GlobalDebug.Info,
833                                     "ADDNLinkedAttrSet",
834                                     "ExpandForeignGroupEnumerator: there are {0} foreignGroups",
835                                     _foreignGroups.Count);
836 
837             GroupPrincipal foreignGroup = _foreignGroups[0];
838             _foreignGroups.RemoveAt(0);
839 
840             // Since members of AD groups must be AD objects
841             Debug.Assert(foreignGroup.Context.QueryCtx is ADStoreCtx);
842             Debug.Assert(foreignGroup.UnderlyingObject is DirectoryEntry);
843             Debug.Assert(((DirectoryEntry)foreignGroup.UnderlyingObject).Path.StartsWith("LDAP:", StringComparison.Ordinal));
844 
845             _storeCtx = (ADStoreCtx)foreignGroup.Context.QueryCtx;
846 
847             //Here the foreignGroup object is removed from the foreignGroups collection.
848             //and not used anymore. Hence, configure RangeRetriever to dispose the DirEntry on its dispose.
849             _membersQueue.Enqueue(new RangeRetriever((DirectoryEntry)foreignGroup.UnderlyingObject, "member", true));
850 
851             string groupDN = (string)((DirectoryEntry)foreignGroup.UnderlyingObject).Properties["distinguishedName"].Value;
852             _groupsVisited.Add(groupDN);
853 
854             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "ExpandForeignGroupEnumerator: recursively processing {0}", groupDN);
855 
856             return true;
857         }
858 
ExpandForeignGroupSearcher()859         private bool ExpandForeignGroupSearcher()
860         {
861             Debug.Assert(_recursive == true);
862             GlobalDebug.WriteLineIf(GlobalDebug.Info,
863                                     "ADDNLinkedAttrSet",
864                                     "ExpandForeignGroupSearcher: there are {0} foreignGroups",
865                                     _foreignGroups.Count);
866 
867             GroupPrincipal foreignGroup = _foreignGroups[0];
868             _foreignGroups.RemoveAt(0);
869 
870             // Since members of AD groups must be AD objects
871             Debug.Assert(foreignGroup.Context.QueryCtx is ADStoreCtx);
872             Debug.Assert(foreignGroup.UnderlyingObject is DirectoryEntry);
873             Debug.Assert(((DirectoryEntry)foreignGroup.UnderlyingObject).Path.StartsWith("LDAP:", StringComparison.Ordinal));
874 
875             _storeCtx = (ADStoreCtx)foreignGroup.Context.QueryCtx;
876 
877             // Queue up a searcher for the new group expansion.
878             DirectorySearcher ds = SDSUtils.ConstructSearcher((DirectoryEntry)foreignGroup.UnderlyingObject);
879             ds.Filter = "(objectClass=*)";
880             ds.SearchScope = SearchScope.Base;
881             ds.AttributeScopeQuery = "member";
882             ds.CacheResults = false;
883 
884             _memberSearchersQueue.Enqueue(ds);
885 
886             string groupDN = (string)((DirectoryEntry)foreignGroup.UnderlyingObject).Properties["distinguishedName"].Value;
887             _groupsVisited.Add(groupDN);
888 
889             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "ExpandForeignGroupSearcher: recursively processing {0}", groupDN);
890 
891             return true;
892         }
893 
MoveNextQueryPrimaryGroupMember()894         private bool MoveNextQueryPrimaryGroupMember()
895         {
896             bool f = false;
897 
898             if (_primaryGroupMembersSearcher != null)
899             {
900                 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: have a searcher");
901 
902                 if (_queryMembersResults == null)
903                 {
904                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "MoveNextQueryMember: issuing query");
905 
906                     _queryMembersResults = _primaryGroupMembersSearcher.FindAll();
907 
908                     Debug.Assert(_queryMembersResults != null);
909 
910                     _queryMembersResultEnumerator = _queryMembersResults.GetEnumerator();
911                 }
912 
913                 f = _queryMembersResultEnumerator.MoveNext();
914 
915                 if (f)
916                 {
917                     this.current = (SearchResult)_queryMembersResultEnumerator.Current;
918                     Debug.Assert(this.current != null);
919 
920                     _currentForeignDE = null;
921                     _currentForeignPrincipal = null;
922 
923                     GlobalDebug.WriteLineIf(GlobalDebug.Info,
924                                             "ADDNLinkedAttrSet",
925                                             "MoveNextQueryMember: got a result, using as current {0}",
926                                             ((SearchResult)this.current).Path);
927                 }
928             }
929 
930             return f;
931         }
932 
933         // Resets the enumerator to before the first result in the set.  This potentially can be an expensive
934         // operation, e.g., if doing a paged search, may need to re-retrieve the first page of results.
935         // As a special case, if the ResultSet is already at the very beginning, this is guaranteed to be
936         // a no-op.
Reset()937         override internal void Reset()
938         {
939             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Reset");
940 
941             if (!_atBeginning)
942             {
943                 _usersVisited.Clear();
944                 _groupsToVisit.Clear();
945                 string originalGroupDN = _groupsVisited[0];
946                 _groupsVisited.Clear();
947                 _groupsVisited.Add(originalGroupDN);
948 
949                 // clear the current enumerator
950                 _members = null;
951                 _membersEnum = null;
952 
953                 // replace all items in the queue with the originals and reset them.
954                 if (null != _originalMembers)
955                 {
956                     _membersQueue.Clear();
957                     foreach (IEnumerable ie in _originalMembers)
958                     {
959                         _membersQueue.Enqueue(ie);
960                         IEnumerator enumerator = ie.GetEnumerator();
961                         enumerator.Reset();
962                     }
963                 }
964 
965                 _expansionMode = _originalExpansionMode;
966 
967                 _storeCtx = _originalStoreCtx;
968 
969                 this.current = null;
970                 if (_primaryGroupDN != null)
971                     _returnedPrimaryGroup = false;
972 
973                 _foreignMembersCurrentGroup.Clear();
974                 _fakePrincipalMembers.Clear();
975 
976                 if (null != _foreignMembersToReturn)
977                     _foreignMembersToReturn.Clear();
978 
979                 _currentForeignPrincipal = null;
980                 _currentForeignDE = null;
981 
982                 _foreignGroups.Clear();
983 
984                 _queryMembersResultEnumerator = null;
985                 if (_queryMembersResults != null)
986                 {
987                     _queryMembersResults.Dispose();
988                     _queryMembersResults = null;
989                 }
990 
991                 if (null != _currentMembersSearcher)
992                 {
993                     _currentMembersSearcher.Dispose();
994                     _currentMembersSearcher = null;
995                 }
996 
997                 _memberSearchResultsEnumerator = null;
998                 if (_memberSearchResults != null)
999                 {
1000                     _memberSearchResults.Dispose();
1001                     _memberSearchResults = null;
1002                 }
1003 
1004                 if (null != _memberSearchersQueue)
1005                 {
1006                     foreach (DirectorySearcher ds in _memberSearchersQueue)
1007                     {
1008                         ds.Dispose();
1009                     }
1010 
1011                     _memberSearchersQueue.Clear();
1012 
1013                     if (null != _memberSearchersQueueOriginal)
1014                     {
1015                         foreach (DirectorySearcher ds in _memberSearchersQueueOriginal)
1016                         {
1017                             _memberSearchersQueue.Enqueue(ds);
1018                         }
1019                     }
1020                 }
1021 
1022                 _atBeginning = true;
1023             }
1024         }
1025 
BookmarkAndReset()1026         override internal ResultSetBookmark BookmarkAndReset()
1027         {
1028             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Bookmarking");
1029 
1030             ADDNLinkedAttrSetBookmark bookmark = new ADDNLinkedAttrSetBookmark();
1031 
1032             bookmark.usersVisited = _usersVisited;
1033             _usersVisited = new Dictionary<string, bool>();
1034 
1035             bookmark.groupsToVisit = _groupsToVisit;
1036             _groupsToVisit = new List<string>();
1037 
1038             string originalGroupDN = _groupsVisited[0];
1039             bookmark.groupsVisited = _groupsVisited;
1040             _groupsVisited = new List<string>();
1041             _groupsVisited.Add(originalGroupDN);
1042 
1043             bookmark.expansionMode = _expansionMode;
1044 
1045             // bookmark the current enumerators
1046             bookmark.members = _members;
1047             bookmark.membersEnum = _membersEnum;
1048 
1049             // Clear the current enumerators for reset
1050             _members = null;
1051             _membersEnum = null;
1052 
1053             // Copy all enumerators in the queue over to the bookmark queue.
1054             if (null != _membersQueue)
1055             {
1056                 bookmark.membersQueue = new Queue<IEnumerable>(_membersQueue.Count);
1057                 foreach (IEnumerable ie in _membersQueue)
1058                 {
1059                     bookmark.membersQueue.Enqueue(ie);
1060                 }
1061             }
1062 
1063             // Refill the original queue with the original enumerators and reset them
1064             if (null != _membersQueue)
1065             {
1066                 _membersQueue.Clear();
1067 
1068                 if (_originalMembers != null)
1069                 {
1070                     foreach (IEnumerable ie in _originalMembers)
1071                     {
1072                         _membersQueue.Enqueue(ie);
1073                         IEnumerator enumerator = ie.GetEnumerator();
1074                         enumerator.Reset();
1075                     }
1076                 }
1077             }
1078 
1079             bookmark.storeCtx = _storeCtx;
1080 
1081             _expansionMode = _originalExpansionMode;
1082 
1083             if (null != _currentMembersSearcher)
1084             {
1085                 _currentMembersSearcher.Dispose();
1086                 _currentMembersSearcher = null;
1087             }
1088 
1089             _storeCtx = _originalStoreCtx;
1090 
1091             bookmark.current = this.current;
1092             bookmark.returnedPrimaryGroup = _returnedPrimaryGroup;
1093             this.current = null;
1094             if (_primaryGroupDN != null)
1095                 _returnedPrimaryGroup = false;
1096 
1097             bookmark.foreignMembersCurrentGroup = _foreignMembersCurrentGroup;
1098             bookmark.fakePrincipalMembers = _fakePrincipalMembers;
1099             bookmark.foreignMembersToReturn = _foreignMembersToReturn;
1100             bookmark.currentForeignPrincipal = _currentForeignPrincipal;
1101             bookmark.currentForeignDE = _currentForeignDE;
1102             _foreignMembersCurrentGroup = new List<DirectoryEntry>();
1103             _fakePrincipalMembers = new List<DirectoryEntry>();
1104             _currentForeignDE = null;
1105 
1106             bookmark.foreignGroups = _foreignGroups;
1107             _foreignGroups = new List<GroupPrincipal>();
1108 
1109             bookmark.queryMembersResults = _queryMembersResults;
1110             bookmark.queryMembersResultEnumerator = _queryMembersResultEnumerator;
1111             _queryMembersResults = null;
1112             _queryMembersResultEnumerator = null;
1113 
1114             bookmark.memberSearchResults = _memberSearchResults;
1115             bookmark.memberSearchResultsEnumerator = _memberSearchResultsEnumerator;
1116             _memberSearchResults = null;
1117             _memberSearchResultsEnumerator = null;
1118 
1119             if (null != _memberSearchersQueue)
1120             {
1121                 bookmark.memberSearcherQueue = new Queue<DirectorySearcher>(_memberSearchersQueue.Count);
1122 
1123                 foreach (DirectorySearcher ds in _memberSearchersQueue)
1124                 {
1125                     bookmark.memberSearcherQueue.Enqueue(ds);
1126                 }
1127             }
1128 
1129             if (null != _memberSearchersQueueOriginal)
1130             {
1131                 _memberSearchersQueue.Clear();
1132 
1133                 foreach (DirectorySearcher ds in _memberSearchersQueueOriginal)
1134                 {
1135                     _memberSearchersQueue.Enqueue(ds);
1136                 }
1137             }
1138 
1139             bookmark.atBeginning = _atBeginning;
1140             _atBeginning = true;
1141 
1142             return bookmark;
1143         }
1144 
RestoreBookmark(ResultSetBookmark bookmark)1145         override internal void RestoreBookmark(ResultSetBookmark bookmark)
1146         {
1147             GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Restoring from bookmark");
1148 
1149             Debug.Assert(bookmark is ADDNLinkedAttrSetBookmark);
1150             ADDNLinkedAttrSetBookmark adBookmark = (ADDNLinkedAttrSetBookmark)bookmark;
1151 
1152             _usersVisited = adBookmark.usersVisited;
1153             _groupsToVisit = adBookmark.groupsToVisit;
1154             _groupsVisited = adBookmark.groupsVisited;
1155             _storeCtx = adBookmark.storeCtx;
1156             this.current = adBookmark.current;
1157             _returnedPrimaryGroup = adBookmark.returnedPrimaryGroup;
1158             _foreignMembersCurrentGroup = adBookmark.foreignMembersCurrentGroup;
1159             _fakePrincipalMembers = adBookmark.fakePrincipalMembers;
1160             _foreignMembersToReturn = adBookmark.foreignMembersToReturn;
1161             _currentForeignPrincipal = adBookmark.currentForeignPrincipal;
1162             _currentForeignDE = adBookmark.currentForeignDE;
1163             _foreignGroups = adBookmark.foreignGroups;
1164             if (_queryMembersResults != null)
1165                 _queryMembersResults.Dispose();
1166             _queryMembersResults = adBookmark.queryMembersResults;
1167             _queryMembersResultEnumerator = adBookmark.queryMembersResultEnumerator;
1168             _memberSearchResults = adBookmark.memberSearchResults;
1169             _memberSearchResultsEnumerator = adBookmark.memberSearchResultsEnumerator;
1170             _atBeginning = adBookmark.atBeginning;
1171             _expansionMode = adBookmark.expansionMode;
1172 
1173             // Replace enumerators
1174             _members = adBookmark.members;
1175             _membersEnum = adBookmark.membersEnum;
1176 
1177             // Replace the enumerator queue elements
1178             if (null != _membersQueue)
1179             {
1180                 _membersQueue.Clear();
1181 
1182                 if (null != adBookmark.membersQueue)
1183                 {
1184                     foreach (IEnumerable ie in adBookmark.membersQueue)
1185                     {
1186                         _membersQueue.Enqueue(ie);
1187                     }
1188                 }
1189             }
1190 
1191             if (null != _memberSearchersQueue)
1192             {
1193                 foreach (DirectorySearcher ds in _memberSearchersQueue)
1194                 {
1195                     ds.Dispose();
1196                 }
1197 
1198                 _memberSearchersQueue.Clear();
1199 
1200                 if (null != adBookmark.memberSearcherQueue)
1201                 {
1202                     foreach (DirectorySearcher ds in adBookmark.memberSearcherQueue)
1203                     {
1204                         _memberSearchersQueue.Enqueue(ds);
1205                     }
1206                 }
1207             }
1208         }
1209 
1210         // IDisposable implementation
Dispose()1211         public override void Dispose()
1212         {
1213             try
1214             {
1215                 if (!_disposed)
1216                 {
1217                     GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing");
1218 
1219                     if (_primaryGroupMembersSearcher != null)
1220                     {
1221                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing primaryGroupMembersSearcher");
1222                         _primaryGroupMembersSearcher.Dispose();
1223                     }
1224 
1225                     if (_queryMembersResults != null)
1226                     {
1227                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing queryMembersResults");
1228                         _queryMembersResults.Dispose();
1229                     }
1230 
1231                     if (_currentMembersSearcher != null)
1232                     {
1233                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersSearcher");
1234                         _currentMembersSearcher.Dispose();
1235                     }
1236 
1237                     if (_memberSearchResults != null)
1238                     {
1239                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing memberSearchResults");
1240                         _memberSearchResults.Dispose();
1241                     }
1242 
1243                     if (_memberSearchersQueue != null)
1244                     {
1245                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing memberSearchersQueue");
1246                         foreach (DirectorySearcher ds in _memberSearchersQueue)
1247                         {
1248                             ds.Dispose();
1249                         }
1250 
1251                         _memberSearchersQueue.Clear();
1252                     }
1253                     IDisposable disposableMembers = _members as IDisposable;
1254                     if (disposableMembers != null)
1255                     {
1256                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing members Enumerable");
1257                         disposableMembers.Dispose();
1258                     }
1259                     IDisposable disposableMembersEnum = _membersEnum as IDisposable;
1260                     if (disposableMembersEnum != null)
1261                     {
1262                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersEnum Enumerator");
1263                         disposableMembersEnum.Dispose();
1264                     }
1265                     if (_membersQueue != null)
1266                     {
1267                         GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADDNLinkedAttrSet", "Dispose: disposing membersQueue");
1268                         foreach (IEnumerable enumerable in _membersQueue)
1269                         {
1270                             IDisposable disposableEnum = enumerable as IDisposable;
1271                             if (disposableEnum != null)
1272                             {
1273                                 disposableEnum.Dispose();
1274                             }
1275                         }
1276                     }
1277                     if (_foreignGroups != null)
1278                     {
1279                         foreach (GroupPrincipal gp in _foreignGroups)
1280                         {
1281                             gp.Dispose();
1282                         }
1283                     }
1284 
1285                     _disposed = true;
1286                 }
1287             }
1288             finally
1289             {
1290                 base.Dispose();
1291             }
1292         }
1293 
1294         //
1295         //
1296         //
1297 
1298         private UnsafeNativeMethods.IADsPathname _pathCracker = null;
1299         private object _pathLock = new object();
1300         private Dictionary<string, bool> _usersVisited = new Dictionary<string, bool>();
1301 
1302         // The 0th entry in this list is always the DN of the original group/user whose membership we're querying
1303         private List<string> _groupsVisited = new List<string>();
1304 
1305         private List<string> _groupsToVisit = new List<string>();
1306 
1307         protected Object current = null; // current member of the group (or current group of the user)
1308 
1309         private bool _returnedPrimaryGroup = false;
1310         private string _primaryGroupDN;                      // the DN of the user's PrimaryGroup (not included in this.members/originalMembers)
1311 
1312         private bool _recursive;
1313 
1314         private Queue<IEnumerable> _membersQueue = new Queue<IEnumerable>();
1315         private IEnumerable _members;            // the membership we're currently enumerating over
1316         private Queue<IEnumerable> _originalMembers = new Queue<IEnumerable>();    // the membership we started off with (before recursing)
1317 
1318         private IEnumerator _membersEnum = null;
1319 
1320         private ADStoreCtx _storeCtx;
1321         private ADStoreCtx _originalStoreCtx;
1322 
1323         private bool _atBeginning = true;
1324 
1325         private bool _disposed = false;
1326 
1327         // foreign
1328         // This contains a list of employees built while enumerating the current group.  These are FSP objects in the current domain and need to
1329         // be translated to find out the domain that holds the actual object.
1330         private List<DirectoryEntry> _foreignMembersCurrentGroup = new List<DirectoryEntry>();
1331         // List of objects from the group tha are actual fake group objects.
1332         private List<DirectoryEntry> _fakePrincipalMembers = new List<DirectoryEntry>();
1333         // list of SIDs + store that have been translated.  These could be any principal object
1334         private SidList _foreignMembersToReturn = null;
1335 
1336         private Principal _currentForeignPrincipal = null;
1337         private DirectoryEntry _currentForeignDE = null;
1338 
1339         private List<GroupPrincipal> _foreignGroups = new List<GroupPrincipal>();
1340 
1341         // members based on a query (used for users who are group members by virtue of their primaryGroupId pointing to the group)
1342         private DirectorySearcher _primaryGroupMembersSearcher;
1343         private SearchResultCollection _queryMembersResults = null;
1344         private IEnumerator _queryMembersResultEnumerator = null;
1345 
1346         private DirectorySearcher _currentMembersSearcher = null;
1347 
1348         private Queue<DirectorySearcher> _memberSearchersQueue = new Queue<DirectorySearcher>();
1349         private Queue<DirectorySearcher> _memberSearchersQueueOriginal = new Queue<DirectorySearcher>();
1350 
1351         private SearchResultCollection _memberSearchResults = null;
1352         private IEnumerator _memberSearchResultsEnumerator = null;
1353 
1354         private ExpansionMode _expansionMode;
1355         private ExpansionMode _originalExpansionMode;
1356 
BuildPathFromDN(string dn)1357         private string BuildPathFromDN(string dn)
1358         {
1359             string userSuppliedServername = _storeCtx.UserSuppliedServerName;
1360 
1361             if (null == _pathCracker)
1362             {
1363                 lock (_pathLock)
1364                 {
1365                     if (null == _pathCracker)
1366                     {
1367                         UnsafeNativeMethods.Pathname pathNameObj = new UnsafeNativeMethods.Pathname();
1368                         _pathCracker = (UnsafeNativeMethods.IADsPathname)pathNameObj;
1369                         _pathCracker.EscapedMode = 2 /* ADS_ESCAPEDMODE_ON */;
1370                     }
1371                 }
1372             }
1373 
1374             _pathCracker.Set(dn, 4 /* ADS_SETTYPE_DN */);
1375 
1376             string escapedDn = _pathCracker.Retrieve(7 /* ADS_FORMAT_X500_DN */);
1377 
1378             if (userSuppliedServername.Length > 0)
1379                 return "LDAP://" + _storeCtx.UserSuppliedServerName + "/" + escapedDn;
1380             else
1381                 return "LDAP://" + escapedDn;
1382         }
1383     }
1384 
1385     internal enum ExpansionMode
1386     {
1387         Enum = 0,
1388         ASQ = 1,
1389     }
1390 
1391     internal class ADDNLinkedAttrSetBookmark : ResultSetBookmark
1392     {
1393         public Dictionary<string, bool> usersVisited;
1394         public List<string> groupsToVisit;
1395         public List<string> groupsVisited;
1396         public IEnumerable members;
1397         public IEnumerator membersEnum = null;
1398         public Queue<IEnumerable> membersQueue;
1399         public ADStoreCtx storeCtx;
1400         public Object current;
1401         public bool returnedPrimaryGroup;
1402         public List<DirectoryEntry> foreignMembersCurrentGroup;
1403         public List<DirectoryEntry> fakePrincipalMembers;
1404         public SidList foreignMembersToReturn;
1405         public Principal currentForeignPrincipal;
1406         public DirectoryEntry currentForeignDE;
1407         public List<GroupPrincipal> foreignGroups;
1408         public SearchResultCollection queryMembersResults;
1409         public IEnumerator queryMembersResultEnumerator;
1410         public SearchResultCollection memberSearchResults;
1411         public IEnumerator memberSearchResultsEnumerator;
1412         public bool atBeginning;
1413         public ExpansionMode expansionMode;
1414         public Queue<DirectorySearcher> memberSearcherQueue;
1415     }
1416 }
1417 
1418 // #endif
1419