Skip to content

Commit b6c6304

Browse files
authored
Fix #41618 Correct marshalling of SortKey objects on Linux (#65548)
1 parent 96ef47b commit b6c6304

File tree

9 files changed

+158
-7
lines changed

9 files changed

+158
-7
lines changed

src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Note:
9494
<User>cn=admin,dc=example,dc=com</User>
9595
<Password>password</Password>
9696
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
97+
<SupportsServerSideSort>True</SupportsServerSideSort>
9798
</Connection>
9899
<Connection Name="SLAPD OPENLDAP SERVER">
99100
<ServerName>localhost</ServerName>
@@ -102,6 +103,7 @@ Note:
102103
<User>cn=admin,dc=example,dc=com</User>
103104
<Password>password</Password>
104105
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
106+
<SupportsServerSideSort>False</SupportsServerSideSort>
105107
</Connection>
106108
<Connection Name="ACTIVE DIRECTORY SERVER">
107109
<ServerName>danmose-ldap.danmose-domain.com</ServerName>
@@ -110,6 +112,7 @@ Note:
110112
<User>danmose-domain\Administrator</User>
111113
<Password>%TESTPASSWORD%</Password>
112114
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
115+
<SupportsServerSideSort>True</SupportsServerSideSort>
113116
</Connection>
114117
<Connection Name="SLAPD OPENLDAP SERVER TLS">
115118
<ServerName>ldap.local</ServerName>
@@ -119,6 +122,7 @@ Note:
119122
<Password>password</Password>
120123
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
121124
<UseTls>true</UseTls>
125+
<SupportsServerSideSort>False</SupportsServerSideSort>
122126
</Connection>
123127

124128
</Configuration>

src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace System.DirectoryServices.Tests
1010
{
1111
internal class LdapConfiguration
1212
{
13-
private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls)
13+
private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls, bool supportsServerSideSort)
1414
{
1515
ServerName = serverName;
1616
SearchDn = searchDn;
@@ -19,6 +19,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
1919
Port = port;
2020
AuthenticationTypes = at;
2121
UseTls = useTls;
22+
SupportsServerSideSort = supportsServerSideSort;
2223
}
2324

2425
private static LdapConfiguration s_ldapConfiguration = GetConfiguration("LDAP.Configuration.xml");
@@ -32,6 +33,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
3233
internal string SearchDn { get; set; }
3334
internal AuthenticationTypes AuthenticationTypes { get; set; }
3435
internal bool UseTls { get; set; }
36+
internal bool SupportsServerSideSort { get; set; }
3537
internal string LdapPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{SearchDn}" : $"LDAP://{ServerName}:{Port}/{SearchDn}";
3638
internal string RootDSEPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/rootDSE" : $"LDAP://{ServerName}:{Port}/rootDSE";
3739
internal string UserNameWithNoDomain
@@ -107,6 +109,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
107109
string password = "";
108110
AuthenticationTypes at = AuthenticationTypes.None;
109111
bool useTls = false;
112+
bool supportsServerSideSort = false;
110113

111114
XElement child = connection.Element("ServerName");
112115
if (child != null)
@@ -141,6 +144,12 @@ internal static LdapConfiguration GetConfiguration(string configFile)
141144
useTls = bool.Parse(child.Value);
142145
}
143146

147+
child = connection.Element("SupportsServerSideSort");
148+
if (child != null)
149+
{
150+
supportsServerSideSort = bool.Parse(child.Value);
151+
}
152+
144153
child = connection.Element("AuthenticationTypes");
145154
if (child != null)
146155
{
@@ -170,7 +179,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
170179
at |= AuthenticationTypes.Signing;
171180
}
172181

173-
ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls);
182+
ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls, supportsServerSideSort);
174183
}
175184
}
176185
catch (Exception ex)

src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapPartialResultsProcessor.cs" />
4343
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapSessionOptions.cs" />
4444
<Compile Include="System\DirectoryServices\Protocols\Interop\SafeHandles.cs" />
45+
<Compile Include="System\DirectoryServices\Protocols\Interop\SortKeyInterop.cs" />
4546
<Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs"
4647
Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
4748
<Link>Common\DisableRuntimeMarshalling.cs</Link>
@@ -58,6 +59,7 @@
5859
<Compile Include="System\DirectoryServices\Protocols\common\QuotaControl.Windows.cs" />
5960
<Compile Include="System\DirectoryServices\Protocols\Interop\LdapPal.Windows.cs" />
6061
<Compile Include="System\DirectoryServices\Protocols\Interop\BerPal.Windows.cs" />
62+
<Compile Include="System\DirectoryServices\Protocols\Interop\SortKeyInterop.Windows.cs" />
6163
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapConnection.Windows.cs" />
6264
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapSessionOptions.Windows.cs" />
6365
<Compile Include="System\DirectoryServices\Protocols\Interop\SafeHandles.Windows.cs" />
@@ -76,6 +78,7 @@
7678
<Compile Include="System\DirectoryServices\Protocols\common\QuotaControl.Linux.cs" />
7779
<Compile Include="System\DirectoryServices\Protocols\Interop\LdapPal.Linux.cs" />
7880
<Compile Include="System\DirectoryServices\Protocols\Interop\BerPal.Linux.cs" />
81+
<Compile Include="System\DirectoryServices\Protocols\Interop\SortKeyInterop.Linux.cs" />
7982
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapConnection.Linux.cs" />
8083
<Compile Include="System\DirectoryServices\Protocols\ldap\LdapSessionOptions.Linux.cs" />
8184
<Compile Include="System\DirectoryServices\Protocols\ldap\LocalAppContextSwitches.cs" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
4+
using System.Collections;
5+
using System.ComponentModel;
6+
using System.Diagnostics;
7+
using System.Text;
8+
using System.Runtime.InteropServices;
9+
using System.Security.Principal;
10+
using System.Runtime.Versioning;
11+
12+
namespace System.DirectoryServices.Protocols
13+
{
14+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
15+
internal partial struct SortKeyInterop
16+
{
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
4+
using System.Collections;
5+
using System.ComponentModel;
6+
using System.Diagnostics;
7+
using System.Text;
8+
using System.Runtime.InteropServices;
9+
using System.Security.Principal;
10+
using System.Runtime.Versioning;
11+
12+
namespace System.DirectoryServices.Protocols
13+
{
14+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
15+
internal partial struct SortKeyInterop
16+
{
17+
}
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
4+
using System.Collections;
5+
using System.ComponentModel;
6+
using System.Diagnostics;
7+
using System.Text;
8+
using System.Runtime.InteropServices;
9+
using System.Security.Principal;
10+
using System.Runtime.Versioning;
11+
12+
namespace System.DirectoryServices.Protocols
13+
{
14+
// Declared as partial in order to be able to set the different StructLayout
15+
// attributes in the Windows and Linux specific files.
16+
// This is a layout-controlled struct, do not alter property ordering.
17+
internal partial struct SortKeyInterop
18+
{
19+
public SortKeyInterop(SortKey sortKey)
20+
{
21+
if (sortKey == null)
22+
throw new ArgumentNullException(nameof(sortKey));
23+
24+
AttributeName = sortKey.AttributeName;
25+
MatchingRule = sortKey.MatchingRule;
26+
ReverseOrder = sortKey.ReverseOrder;
27+
}
28+
29+
internal string AttributeName { get; set; }
30+
31+
internal string MatchingRule { get; set; }
32+
33+
internal bool ReverseOrder { get; set; }
34+
}
35+
}

src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,9 +703,15 @@ public SortKey[] SortKeys
703703

704704
public override byte[] GetValue()
705705
{
706+
SortKeyInterop[] nativeSortKeys = new SortKeyInterop[_keys.Length];
707+
for (int i = 0; i < _keys.Length; ++i)
708+
{
709+
nativeSortKeys[i] = new SortKeyInterop(_keys[i]);
710+
}
711+
706712
IntPtr control = IntPtr.Zero;
707-
int structSize = Marshal.SizeOf(typeof(SortKey));
708-
int keyCount = _keys.Length;
713+
int structSize = Marshal.SizeOf(typeof(SortKeyInterop));
714+
int keyCount = nativeSortKeys.Length;
709715
IntPtr memHandle = Utility.AllocHGlobalIntPtrArray(keyCount + 1);
710716

711717
try
@@ -716,7 +722,7 @@ public override byte[] GetValue()
716722
for (i = 0; i < keyCount; i++)
717723
{
718724
sortPtr = Marshal.AllocHGlobal(structSize);
719-
Marshal.StructureToPtr(_keys[i], sortPtr, false);
725+
Marshal.StructureToPtr(nativeSortKeys[i], sortPtr, false);
720726
tempPtr = (IntPtr)((long)memHandle + IntPtr.Size * i);
721727
Marshal.WriteIntPtr(tempPtr, sortPtr);
722728
}

src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public partial class DirectoryServicesProtocolsTests
1515
internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null;
1616
internal static bool IsActiveDirectoryServer => IsLdapConfigurationExist && LdapConfiguration.Configuration.IsActiveDirectoryServer;
1717

18+
internal static bool IsServerSideSortSupported => IsLdapConfigurationExist && LdapConfiguration.Configuration.SupportsServerSideSort;
19+
1820
[ConditionalFact(nameof(IsLdapConfigurationExist))]
1921
public void TestInvalidFilter()
2022
{
@@ -533,6 +535,58 @@ public void TestPageRequests()
533535
}
534536
}
535537

538+
[ConditionalFact(nameof(IsServerSideSortSupported))]
539+
public void TestSortedSearch()
540+
{
541+
using (LdapConnection connection = GetConnection())
542+
{
543+
string ouName = "ProtocolsGroup10";
544+
string dn = "ou=" + ouName;
545+
546+
try
547+
{
548+
for (int i=0; i<10; i++)
549+
{
550+
DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
551+
}
552+
DeleteEntry(connection, dn);
553+
554+
AddOrganizationalUnit(connection, dn);
555+
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
556+
Assert.NotNull(sre);
557+
558+
for (int i=0; i<10; i++)
559+
{
560+
AddOrganizationalUnit(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
561+
}
562+
563+
string filter = "(objectClass=*)";
564+
SearchRequest searchRequest = new SearchRequest(
565+
dn + "," + LdapConfiguration.Configuration.SearchDn,
566+
filter,
567+
SearchScope.Subtree,
568+
null);
569+
570+
var sortRequestControl = new SortRequestControl("ou", true);
571+
searchRequest.Controls.Add(sortRequestControl);
572+
573+
SearchResponse searchResponse = (SearchResponse) connection.SendRequest(searchRequest);
574+
Assert.Equal(1, searchResponse.Controls.Length);
575+
Assert.True(searchResponse.Controls[0] is SortResponseControl);
576+
Assert.True(searchResponse.Entries.Count > 0);
577+
Assert.Equal("ou=ProtocolsSubGroup10.9," + dn + "," + LdapConfiguration.Configuration.SearchDn, searchResponse.Entries[0].DistinguishedName);
578+
}
579+
finally
580+
{
581+
for (int i=0; i<20; i++)
582+
{
583+
DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
584+
}
585+
DeleteEntry(connection, dn);
586+
}
587+
}
588+
}
589+
536590
private void DeleteAttribute(LdapConnection connection, string entryDn, string attributeName)
537591
{
538592
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;

src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,15 @@ public void Ctor_SortKeys(bool critical)
3232

3333
control.IsCritical = critical;
3434
var expected = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ?
35+
// WLDAP formatted ASN.1
3536
new byte[] { 48, 132, 0, 0, 0, 43, 48, 132, 0, 0, 0, 17, 4, 5,110,
3637
97, 109, 101, 49, 128, 5, 114, 117, 108, 101, 49, 129,
3738
1, 255, 48, 132, 0, 0, 0, 14, 4, 5, 110, 97, 109, 101,
38-
50, 128, 5, 114, 117, 108, 101, 50} :
39-
new byte[] { 48, 19, 48, 9, 4, 1, 110, 128, 1, 114, 129, 1, 255, 48, 6, 4, 1, 110, 128, 1, 114 };
39+
50, 128, 5, 114, 117, 108, 101, 50 } :
40+
// OpenLdap formatted ASN.1
41+
new byte[] { 48, 35, 48, 17, 4, 5, 110, 97, 109, 101, 49, 128, 5,
42+
114, 117, 108, 101, 49, 129, 1, 255, 48, 14, 4, 5, 110, 97, 109,
43+
101, 50, 128, 5, 114, 117, 108, 101, 50 };
4044
Assert.Equal(expected, control.GetValue());
4145
}
4246

0 commit comments

Comments
 (0)