|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.Globalization;
using System.Text.RegularExpressions;
namespace System.DirectoryServices.AccountManagement
{
internal sealed class SAMQuerySet : ResultSet
{
// We will iterate over all principals under ctxBase, returning only those which are in the list of types and which
// satisfy ALL the matching properties.
internal SAMQuerySet(
List<string> schemaTypes,
DirectoryEntries entries,
DirectoryEntry ctxBase,
int sizeLimit,
SAMStoreCtx storeCtx,
SAMMatcher samMatcher)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "SAMQuerySet: creating for path={0}, sizelimit={1}", ctxBase.Path, sizeLimit);
_schemaTypes = schemaTypes;
_entries = entries;
_sizeLimit = sizeLimit; // -1 == no limit
_storeCtx = storeCtx;
_ctxBase = ctxBase;
_matcher = samMatcher;
_enumerator = _entries.GetEnumerator();
}
// Return the principal we're positioned at as a Principal object.
// Need to use our StoreCtx's GetAsPrincipal to convert the native object to a Principal
internal override object CurrentAsPrincipal
{
get
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "CurrentAsPrincipal");
// Since this class is only used internally, none of our code should be even calling this
// if MoveNext returned false, or before calling MoveNext.
Debug.Assert(!_endReached && _current != null);
return SAMUtils.DirectoryEntryAsPrincipal(_current, _storeCtx);
}
}
// Advance the enumerator to the next principal in the result set, pulling in additional pages
// of results (or ranges of attribute values) as needed.
// Returns true if successful, false if no more results to return.
internal override bool MoveNext()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "Entering MoveNext");
Debug.Assert(_enumerator != null);
bool needToRetry = false;
bool f;
// Have we exceeded the requested size limit?
if ((_sizeLimit != -1) && (_resultsReturned >= _sizeLimit))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"SAMQuerySet",
"MoveNext: exceeded sizelimit, ret={0}, limit={1}",
_resultsReturned,
_sizeLimit);
_endReached = true;
}
// End was reached previously. Nothing more to do.
if (_endReached)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "MoveNext: endReached");
return false;
}
// Pull the next result. We may have to repeat this several times
// until we find a result that matches the user's filter.
do
{
f = _enumerator.MoveNext();
needToRetry = false;
if (f)
{
DirectoryEntry entry = (DirectoryEntry)_enumerator.Current;
// Does it match the user's properties?
//
// We'd like to use DirectoryEntries.SchemaFilter rather than calling
// IsOfCorrectType here, but SchemaFilter has a bug
// where multiple DirectoryEntries all share the same SchemaFilter ---
// which would create multithreading issues for us.
if (IsOfCorrectType(entry) && _matcher.Matches(entry))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "MoveNext: found a match on {0}", entry.Path);
// Yes. It's our new current object
_current = entry;
_resultsReturned++;
}
else
{
// No. Retry.
needToRetry = true;
}
}
}
while (needToRetry);
if (!f)
{
/*
// One more to try: the root object
if (IsOfCorrectType(this.ctxBase) && this.matcher.Matches(this.ctxBase))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "MoveNext: found a match on root {0}", this.ctxBase);
this.current = this.ctxBase;
this.resultsReturned++;
f = true;
}
else
{
endReached = true;
}
* */
}
return f;
}
private bool IsOfCorrectType(DirectoryEntry de)
{
// Is the object in question one of the desired types?
foreach (string schemaType in _schemaTypes)
{
if (SAMUtils.IsOfObjectClass(de, schemaType))
return true;
}
return false;
}
// Resets the enumerator to before the first result in the set. This potentially can be an expensive
// operation, e.g., if doing a paged search, may need to re-retrieve the first page of results.
// As a special case, if the ResultSet is already at the very beginning, this is guaranteed to be
// a no-op.
internal override void Reset()
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "Reset");
// if current == null, we're already at the beginning
if (_current != null)
{
_endReached = false;
_current = null;
_enumerator?.Reset();
_resultsReturned = 0;
}
}
//
// Private fields
//
private readonly SAMStoreCtx _storeCtx;
private readonly DirectoryEntry _ctxBase;
private readonly DirectoryEntries _entries;
private readonly IEnumerator _enumerator; // the enumerator for "entries"
private DirectoryEntry _current; // the DirectoryEntry that we're currently positioned at
// Filter parameters
private readonly int _sizeLimit; // -1 == no limit
private readonly List<string> _schemaTypes;
private readonly SAMMatcher _matcher;
// Count of number of results returned so far
private int _resultsReturned;
// Have we run out of entries?
private bool _endReached;
}
internal abstract class SAMMatcher
{
internal abstract bool Matches(DirectoryEntry de);
}
//
// The matcher routines for query-by-example support
//
internal sealed class QbeMatcher : SAMMatcher
{
private readonly QbeFilterDescription _propertiesToMatch;
internal QbeMatcher(QbeFilterDescription propertiesToMatch)
{
_propertiesToMatch = propertiesToMatch;
}
//
// used for initializing static tables
//
private static Hashtable CreateFilterPropertiesTable()
{
//
// Load the filterPropertiesTable
//
var filterPropertiesTable = new Hashtable();
for (int i = 0; i < s_filterPropertiesTableRaw.GetLength(0); i++)
{
Type qbeType = s_filterPropertiesTableRaw[i, 0] as Type;
string winNTPropertyName = s_filterPropertiesTableRaw[i, 1] as string;
MatcherDelegate f = s_filterPropertiesTableRaw[i, 2] as MatcherDelegate;
Debug.Assert(qbeType != null);
Debug.Assert(winNTPropertyName != null);
Debug.Assert(f != null);
// There should only be one entry per QBE type
Debug.Assert(filterPropertiesTable[qbeType] == null);
FilterPropertyTableEntry entry = new FilterPropertyTableEntry();
entry.winNTPropertyName = winNTPropertyName;
entry.matcher = f;
filterPropertiesTable[qbeType] = entry;
}
return filterPropertiesTable;
}
internal override bool Matches(DirectoryEntry de)
{
// If it has no SID, it's not a security principal, and we're not interested in it.
// (In reg-SAM, computers don't have accounts and therefore don't have SIDs, but ADSI
// creates fake Computer objects for them. In LSAM, computers CAN have accounts, and thus
// SIDs).
if (de.Properties["objectSid"] == null || de.Properties["objectSid"].Count == 0)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "SamMatcher: Matches: skipping no-SID {0}", de.Path);
return false;
}
// Try to match each specified property in turn
foreach (FilterBase filter in _propertiesToMatch.FiltersToApply)
{
FilterPropertyTableEntry entry = (FilterPropertyTableEntry)s_filterPropertiesTable[filter.GetType()];
if (entry == null)
{
// Must be a property we don't support
throw new NotSupportedException(
SR.Format(
SR.StoreCtxUnsupportedPropertyForQuery,
PropertyNamesExternal.GetExternalForm(filter.PropertyName)));
}
if (!entry.matcher(filter, entry.winNTPropertyName, de))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "SamMatcher: Matches: no match {0}", de.Path);
return false;
}
}
// All tests pass --- it's a match
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "SamMatcher: Matches: match {0}", de.Path);
return true;
}
// We only list properties we support filtering on in this table. At run-time, if we detect they set a
// property that's not listed here, we throw an exception.
private static readonly object[,] s_filterPropertiesTableRaw =
{
// QbeType WinNT Property Matcher
{typeof(DescriptionFilter), "Description", new MatcherDelegate(StringMatcher)},
{typeof(DisplayNameFilter), "FullName", new MatcherDelegate(StringMatcher)},
{typeof(SidFilter), "objectSid", new MatcherDelegate(SidMatcher)},
{typeof(SamAccountNameFilter), "Name", new MatcherDelegate(SamAccountNameMatcher)},
{typeof(AuthPrincEnabledFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(PermittedWorkstationFilter), "LoginWorkstations", new MatcherDelegate(MultiStringMatcher)},
{typeof(PermittedLogonTimesFilter), "LoginHours", new MatcherDelegate(BinaryMatcher)},
{typeof(ExpirationDateFilter), "AccountExpirationDate", new MatcherDelegate(ExpirationDateMatcher)},
{typeof(SmartcardLogonRequiredFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(DelegationPermittedFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(HomeDirectoryFilter), "HomeDirectory", new MatcherDelegate(StringMatcher)},
{typeof(HomeDriveFilter), "HomeDirDrive", new MatcherDelegate(StringMatcher)},
{typeof(ScriptPathFilter), "LoginScript", new MatcherDelegate(StringMatcher)},
{typeof(PasswordNotRequiredFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(PasswordNeverExpiresFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(CannotChangePasswordFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(AllowReversiblePasswordEncryptionFilter), "UserFlags", new MatcherDelegate(UserFlagsMatcher)},
{typeof(GroupScopeFilter), "groupType", new MatcherDelegate(GroupTypeMatcher)},
{typeof(ExpiredAccountFilter), "AccountExpirationDate", new MatcherDelegate(DateTimeMatcher)},
{typeof(LastLogonTimeFilter), "LastLogin", new MatcherDelegate(DateTimeMatcher)},
{typeof(PasswordSetTimeFilter), "PasswordAge", new MatcherDelegate(DateTimeMatcher)},
{typeof(BadLogonCountFilter), "BadPasswordAttempts", new MatcherDelegate(IntMatcher)},
};
private static readonly Hashtable s_filterPropertiesTable = CreateFilterPropertiesTable();
private sealed class FilterPropertyTableEntry
{
internal string winNTPropertyName;
internal MatcherDelegate matcher;
}
//
// Conversion routines
//
private static bool WildcardStringMatch(FilterBase filter, string wildcardFilter, string property)
{
// Build a Regex that matches valueToMatch, and store it on the Filter (so that we don't have
// to have the CLR constantly reparse the regex string).
// Ideally, we'd like to use a compiled Regex (RegexOptions.Compiled) for performance,
// but the CLR cannot release generated MSIL. Thus, our memory usage would grow without bound
// each time a query was performed.
Regex regex = filter.Extra as Regex;
if (regex == null)
{
regex = new Regex(SAMUtils.PAPIQueryToRegexString(wildcardFilter), RegexOptions.Singleline);
filter.Extra = regex;
}
return regex.IsMatch(property);
}
// returns true if specified WinNT property's value matches filter.Value
private delegate bool MatcherDelegate(FilterBase filter, string winNTPropertyName, DirectoryEntry de);
private static bool DateTimeMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
QbeMatchType valueToMatch = (QbeMatchType)filter.Value;
if (null == valueToMatch.Value)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(de.Properties[winNTPropertyName].Value == null))
return true;
}
else
{
Debug.Assert(valueToMatch.Value is DateTime);
if (de.Properties.Contains(winNTPropertyName) && (de.Properties[winNTPropertyName].Value != null))
{
DateTime value;
if (winNTPropertyName == "PasswordAge")
{
PropertyValueCollection values = de.Properties["PasswordAge"];
if (values.Count != 0)
{
Debug.Assert(values.Count == 1);
Debug.Assert(values[0] is int);
int secondsLapsed = (int)values[0];
value = DateTime.UtcNow - new TimeSpan(0, 0, secondsLapsed);
}
else
{
// If we don't have a passwordAge then this item will never match.
return false;
}
}
else
{
value = (DateTime)de.Properties[winNTPropertyName].Value;
}
int comparisonResult = DateTime.Compare(value, (DateTime)valueToMatch.Value);
bool result = valueToMatch.Match switch
{
MatchType.Equals => comparisonResult == 0,
MatchType.NotEquals => comparisonResult != 0,
MatchType.GreaterThan => comparisonResult > 0,
MatchType.GreaterThanOrEquals => comparisonResult >= 0,
MatchType.LessThan => comparisonResult < 0,
MatchType.LessThanOrEquals => comparisonResult <= 0,
_ => false,
};
return result;
}
}
return false;
}
private static bool StringMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
string valueToMatch = (string)filter.Value;
if (valueToMatch == null)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(((string)de.Properties[winNTPropertyName].Value).Length == 0))
return true;
}
else
{
if (de.Properties.Contains(winNTPropertyName))
{
string value = (string)de.Properties[winNTPropertyName].Value;
if (value != null)
{
return WildcardStringMatch(filter, valueToMatch, value);
}
}
}
return false;
}
private static bool IntMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
QbeMatchType valueToMatch = (QbeMatchType)filter.Value;
bool result = false;
if (null == valueToMatch.Value)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(de.Properties[winNTPropertyName].Value == null))
result = true;
}
else
{
if (de.Properties.Contains(winNTPropertyName))
{
int value = (int)de.Properties[winNTPropertyName].Value;
int comparisonValue = (int)valueToMatch.Value;
result = valueToMatch.Match switch
{
MatchType.Equals => (value == comparisonValue),
MatchType.NotEquals => (value != comparisonValue),
MatchType.GreaterThan => (value > comparisonValue),
MatchType.GreaterThanOrEquals => (value >= comparisonValue),
MatchType.LessThan => (value < comparisonValue),
MatchType.LessThanOrEquals => (value <= comparisonValue),
_ => false,
};
}
}
return result;
}
private static bool SamAccountNameMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
string samToMatch = (string)filter.Value;
int index = samToMatch.IndexOf('\\');
if (index == samToMatch.Length - 1)
throw new InvalidOperationException(SR.StoreCtxNT4IdentityClaimWrongForm);
string samAccountName = (index != -1) ? samToMatch.Substring(index + 1) : // +1 to skip the '/'
samToMatch;
if (de.Properties["Name"].Count > 0 && de.Properties["Name"].Value != null)
{
return WildcardStringMatch(filter, samAccountName, (string)de.Properties["Name"].Value);
/*
return (String.Compare(((string)de.Properties["Name"].Value),
samAccountName,
true, // acct names are not case-sensitive
CultureInfo.InvariantCulture) == 0);
*/
}
return false;
}
private static bool SidMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
byte[] sidToMatch = Utils.StringToByteArray((string)filter.Value);
if (sidToMatch == null)
throw new InvalidOperationException(SR.StoreCtxSecurityIdentityClaimBadFormat);
if (de.Properties["objectSid"].Count > 0 && de.Properties["objectSid"].Value != null)
{
return Utils.AreBytesEqual(sidToMatch, (byte[])de.Properties["objectSid"].Value);
}
return false;
}
private static bool UserFlagsMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
Debug.Assert(winNTPropertyName == "UserFlags");
bool valueToMatch = (bool)filter.Value;
// If it doesn't contain the property, it certainly can't match the user's value
if (!de.Properties.Contains(winNTPropertyName) || de.Properties[winNTPropertyName].Count == 0)
return false;
int value = (int)de.Properties[winNTPropertyName].Value;
switch (filter.PropertyName)
{
// We want to return true iff both value and valueToMatch are true, or both are false
// (i.e., NOT XOR)
case AuthPrincEnabledFilter.PropertyNameStatic:
// UF_ACCOUNTDISABLE
// Note that the logic is inverted on this one. We expose "Enabled",
// but SAM stores it as "Disabled".
return (((value & 0x0002) != 0) ^ valueToMatch);
case SmartcardLogonRequiredFilter.PropertyNameStatic:
// UF_SMARTCARD_REQUIRED
return !(((value & 0x40000) != 0) ^ valueToMatch);
case DelegationPermittedFilter.PropertyNameStatic:
// UF_NOT_DELEGATED
// Note that the logic is inverted on this one. That's because we expose
// "delegation allowed", but AD represents it as the inverse, "delegation NOT allowed"
return (((value & 0x100000) != 0) ^ valueToMatch);
case PasswordNotRequiredFilter.PropertyNameStatic:
// UF_PASSWD_NOTREQD
return !(((value & 0x0020) != 0) ^ valueToMatch);
case PasswordNeverExpiresFilter.PropertyNameStatic:
// UF_DONT_EXPIRE_PASSWD
return !(((value & 0x10000) != 0) ^ valueToMatch);
case CannotChangePasswordFilter.PropertyNameStatic:
// UF_PASSWD_CANT_CHANGE
return !(((value & 0x0040) != 0) ^ valueToMatch);
case AllowReversiblePasswordEncryptionFilter.PropertyNameStatic:
// UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
return !(((value & 0x0080) != 0) ^ valueToMatch);
default:
Debug.Fail("SAMQuerySet.UserFlagsMatcher: fell off end looking for " + filter.PropertyName);
return false;
}
}
private static bool MultiStringMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
string valueToMatch = (string)filter.Value;
if (valueToMatch == null)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(((string)de.Properties[winNTPropertyName].Value).Length == 0))
return true;
}
else
{
if (de.Properties.Contains(winNTPropertyName) && (de.Properties[winNTPropertyName].Count != 0))
{
foreach (string value in de.Properties[winNTPropertyName])
{
if (value != null)
{
return WildcardStringMatch(filter, valueToMatch, value);
}
}
}
}
return false;
}
private static bool BinaryMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
byte[] valueToMatch = (byte[])filter.Value;
if (valueToMatch == null)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(de.Properties[winNTPropertyName].Value == null))
return true;
}
else
{
if (de.Properties.Contains(winNTPropertyName))
{
byte[] value = (byte[])de.Properties[winNTPropertyName].Value;
if ((value != null) && Utils.AreBytesEqual(value, valueToMatch))
return true;
}
}
return false;
}
private static bool ExpirationDateMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
Debug.Assert(filter is ExpirationDateFilter);
Debug.Assert(winNTPropertyName == "AccountExpirationDate");
Nullable<DateTime> valueToCompare = (Nullable<DateTime>)filter.Value;
if (!valueToCompare.HasValue)
{
if (!de.Properties.Contains(winNTPropertyName) ||
(de.Properties[winNTPropertyName].Count == 0) ||
(de.Properties[winNTPropertyName].Value == null))
return true;
}
else
{
if (de.Properties.Contains(winNTPropertyName) && (de.Properties[winNTPropertyName].Value != null))
{
DateTime value = (DateTime)de.Properties[winNTPropertyName].Value;
if (value.Equals(valueToCompare.Value))
return true;
}
}
return false;
}
private static bool GroupTypeMatcher(FilterBase filter, string winNTPropertyName, DirectoryEntry de)
{
Debug.Assert(winNTPropertyName == "groupType");
Debug.Assert(filter is GroupScopeFilter);
GroupScope valueToMatch = (GroupScope)filter.Value;
// All SAM local machine groups are local groups
if (valueToMatch == GroupScope.Local)
return true;
else
return false;
}
}
//
// The matcher routines for FindBy* support
//
internal sealed class FindByDateMatcher : SAMMatcher
{
internal enum DateProperty
{
LogonTime,
PasswordSetTime,
AccountExpirationTime
}
private readonly DateProperty _propertyToMatch;
private readonly MatchType _matchType;
private readonly DateTime _valueToMatch;
internal FindByDateMatcher(DateProperty property, MatchType matchType, DateTime value)
{
_propertyToMatch = property;
_matchType = matchType;
_valueToMatch = value;
}
internal override bool Matches(DirectoryEntry de)
{
// If it has no SID, it's not a security principal, and we're not interested in it.
// (In reg-SAM, computers don't have accounts and therefore don't have SIDs, but ADSI
// creates fake Computer objects for them. In LSAM, computers CAN have accounts, and thus
// SIDs).
if (de.Properties["objectSid"] == null || de.Properties["objectSid"].Count == 0)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "FindByDateMatcher: Matches: skipping no-SID {0}", de.Path);
return false;
}
switch (_propertyToMatch)
{
case DateProperty.LogonTime:
return MatchOnLogonTime(de);
case DateProperty.PasswordSetTime:
return MatchOnPasswordSetTime(de);
case DateProperty.AccountExpirationTime:
return MatchOnAccountExpirationTime(de);
default:
Debug.Fail("FindByDateMatcher.Matches: Fell off end looking for propertyToMatch=" + _propertyToMatch.ToString());
return false;
}
}
private bool MatchOnLogonTime(DirectoryEntry de)
{
PropertyValueCollection values = de.Properties["LastLogin"];
Nullable<DateTime> storeValue = null;
// Get the logon time from the DirectoryEntry
if (values.Count > 0)
{
Debug.Assert(values.Count == 1);
storeValue = (Nullable<DateTime>)values[0];
}
return TestForMatch(storeValue);
}
private bool MatchOnAccountExpirationTime(DirectoryEntry de)
{
PropertyValueCollection values = de.Properties["AccountExpirationDate"];
Nullable<DateTime> storeValue = null;
// Get the expiration date from the DirectoryEntry
if (values.Count > 0)
{
Debug.Assert(values.Count == 1);
storeValue = (Nullable<DateTime>)values[0];
}
return TestForMatch(storeValue);
}
private bool MatchOnPasswordSetTime(DirectoryEntry de)
{
PropertyValueCollection values = de.Properties["PasswordAge"];
Nullable<DateTime> storeValue = null;
if (values.Count != 0)
{
Debug.Assert(values.Count == 1);
Debug.Assert(values[0] is int);
int secondsLapsed = (int)values[0];
storeValue = DateTime.UtcNow - new TimeSpan(0, 0, secondsLapsed);
}
return TestForMatch(storeValue);
}
private bool TestForMatch(Nullable<DateTime> nullableStoreValue)
{
// If the store object doesn't have the property set, then the only
// way it could match is if they asked for a not-equals test
// (if the store object doesn't have a value, then it certainly doesn't match
// whatever value they specified)
if (!nullableStoreValue.HasValue)
return (_matchType == MatchType.NotEquals) ? true : false;
Debug.Assert(nullableStoreValue.HasValue);
DateTime storeValue = nullableStoreValue.Value;
switch (_matchType)
{
case MatchType.Equals:
return (storeValue == _valueToMatch);
case MatchType.NotEquals:
return (storeValue != _valueToMatch);
case MatchType.GreaterThan:
return (storeValue > _valueToMatch);
case MatchType.GreaterThanOrEquals:
return (storeValue >= _valueToMatch);
case MatchType.LessThan:
return (storeValue < _valueToMatch);
case MatchType.LessThanOrEquals:
return (storeValue <= _valueToMatch);
default:
Debug.Fail("FindByDateMatcher.TestForMatch: Fell off end looking for matchType=" + _matchType.ToString());
return false;
}
}
}
internal sealed class GroupMemberMatcher : SAMMatcher
{
private readonly byte[] _memberSidToMatch;
internal GroupMemberMatcher(byte[] memberSidToMatch)
{
Debug.Assert(memberSidToMatch != null);
Debug.Assert(memberSidToMatch.Length != 0);
_memberSidToMatch = memberSidToMatch;
}
internal override bool Matches(DirectoryEntry groupDE)
{
// If it has no SID, it's not a security principal, and we're not interested in it.
// (In reg-SAM, computers don't have accounts and therefore don't have SIDs, but ADSI
// creates fake Computer objects for them. In LSAM, computers CAN have accounts, and thus
// SIDs).
if (groupDE.Properties["objectSid"] == null || groupDE.Properties["objectSid"].Count == 0)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "GroupMemberMatcher: Matches: skipping no-SID group={0}", groupDE.Path);
return false;
}
// Enumerate the members of the group, looking for a match
UnsafeNativeMethods.IADsGroup iADsGroup = (UnsafeNativeMethods.IADsGroup)groupDE.NativeObject;
UnsafeNativeMethods.IADsMembers iADsMembers = iADsGroup.Members();
foreach (UnsafeNativeMethods.IADs nativeMember in ((IEnumerable)iADsMembers))
{
// Wrap the DirectoryEntry around the native ADSI object
// (which already has the correct credentials)
DirectoryEntry memberDE = new DirectoryEntry(nativeMember);
// No SID --> not interesting
if (memberDE.Properties["objectSid"] == null || memberDE.Properties["objectSid"].Count == 0)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "GroupMemberMatcher: Matches: skipping member no-SID member={0}", memberDE.Path);
continue;
}
byte[] memberSid = (byte[])memberDE.Properties["objectSid"].Value;
// Did we find a matching member in the group?
if (Utils.AreBytesEqual(memberSid, _memberSidToMatch))
{
GlobalDebug.WriteLineIf(GlobalDebug.Info,
"SAMQuerySet",
"GroupMemberMatcher: Matches: match member={0}, group={1)",
memberDE.Path,
groupDE.Path);
return true;
}
}
// We tried all the members in the group and didn't get a match on any
GlobalDebug.WriteLineIf(GlobalDebug.Info, "SAMQuerySet", "SamMatcher: Matches: no match, group={0}", groupDE.Path);
return false;
}
}
}
|