Skip to content

Commit b67a91e

Browse files
author
blackhedd
committed
implemented RFC 2696.
1 parent 91631f8 commit b67a91e

File tree

2 files changed

+83
-38
lines changed

2 files changed

+83
-38
lines changed

lib/net/ldap.rb

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ class LdapError < Exception; end
292292
2 => "Protocol Error",
293293
3 => "Time Limit Exceeded",
294294
4 => "Size Limit Exceeded",
295+
12 => "Unavailable crtical extension",
295296
16 => "No Such Attribute",
296297
17 => "Undefined Attribute Type",
297298
20 => "Attribute or Value Exists",
@@ -308,6 +309,12 @@ class LdapError < Exception; end
308309
68 => "Entry Already Exists"
309310
}
310311

312+
313+
module LdapControls
314+
PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
315+
end
316+
317+
311318
#
312319
# LDAP::result2string
313320
#
@@ -869,6 +876,13 @@ def bind auth
869876
#--
870877
# WARNING: this code substantially recapitulates the searchx method.
871878
#
879+
# 02May06: Well, I added support for RFC-2696-style paged searches.
880+
# This is used on all queries because the extension is marked non-critical.
881+
# As far as I know, only A/D uses this, but it's required for A/D. Otherwise
882+
# you won't get more than 1000 results back from a query.
883+
# This implementation is kindof clunky and should probably be refactored.
884+
# Also, is it my imagination, or are A/Ds the slowest directory servers ever???
885+
#
872886
def search args = {}
873887
search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
874888
search_base = (args && args[:base]) || "dc=example,dc=com"
@@ -878,47 +892,73 @@ def search args = {}
878892
scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
879893
raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope)
880894

881-
request = [
882-
search_base.to_ber,
883-
scope.to_ber_enumerated,
884-
0.to_ber_enumerated,
885-
0.to_ber,
886-
0.to_ber,
887-
attributes_only.to_ber,
888-
search_filter.to_ber,
889-
search_attributes.to_ber_sequence
890-
].to_ber_appsequence(3)
891-
892-
=begin
893-
controls = [
894-
[
895-
"1.2.840.113556.1.4.319".to_ber,
896-
false.to_ber,
897-
898-
[10.to_ber, "".to_ber].to_ber_sequence.to_s.to_ber
899-
].to_ber_sequence
900-
901-
].to_ber_contextspecific(0)
902-
903-
pkt = [next_msgid.to_ber, request, controls].to_ber_sequence
904-
=end
905-
pkt = [next_msgid.to_ber, request].to_ber_sequence
906-
@conn.write pkt
907-
895+
rfc2696_cookie = [739, ""] # size-limit is a funky number so we can distinguish it from errors.
908896
result_code = 0
909897

910-
while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
911-
#p be
912-
case pdu.app_tag
913-
when 4 # search-data
914-
yield( pdu.search_entry ) if block_given?
915-
when 5 # search-result
916-
result_code = pdu.result_code
917-
break
918-
else
919-
raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
898+
loop {
899+
# should collect this into a private helper to clarify the structure
900+
901+
request = [
902+
search_base.to_ber,
903+
scope.to_ber_enumerated,
904+
0.to_ber_enumerated,
905+
0.to_ber,
906+
0.to_ber,
907+
attributes_only.to_ber,
908+
search_filter.to_ber,
909+
search_attributes.to_ber_sequence
910+
].to_ber_appsequence(3)
911+
912+
controls = [
913+
[
914+
LdapControls::PagedResults.to_ber,
915+
false.to_ber, # criticality MUST be false to interoperate with normal LDAPs.
916+
rfc2696_cookie.map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber
917+
].to_ber_sequence
918+
].to_ber_contextspecific(0)
919+
920+
pkt = [next_msgid.to_ber, request, controls].to_ber_sequence
921+
@conn.write pkt
922+
923+
result_code = 0
924+
controls = []
925+
926+
while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
927+
case pdu.app_tag
928+
when 4 # search-data
929+
yield( pdu.search_entry ) if block_given?
930+
when 5 # search-result
931+
result_code = pdu.result_code
932+
controls = pdu.result_controls
933+
break
934+
else
935+
raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
936+
end
920937
end
921-
end
938+
939+
# When we get here, we have seen a type-5 response.
940+
# If there is no error AND there is an RFC-2696 cookie,
941+
# then query again for the next page of results.
942+
# If not, we're done.
943+
# Don't screw this up or we'll break every search we do.
944+
more_pages = false
945+
if result_code == 0 and controls
946+
controls.each do |c|
947+
if c.oid == LdapControls::PagedResults
948+
more_pages = false # just in case some bogus server sends us >1 of these.
949+
if c.value and c.value.length > 0
950+
cookie = c.value.read_ber[1]
951+
if cookie and cookie.length > 0
952+
rfc2696_cookie[1] = cookie
953+
more_pages = true
954+
end
955+
end
956+
end
957+
end
958+
end
959+
960+
break unless more_pages
961+
} # loop
922962

923963
result_code
924964
end

lib/net/ldap/pdu.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ def result_code code = :resultCode
109109
@ldap_result and @ldap_result[code]
110110
end
111111

112+
# Return RFC-2251 Controls if any.
113+
# Messy. Does this functionality belong somewhere else?
114+
def result_controls
115+
@ldap_controls || []
116+
end
112117

113118

114119
#

0 commit comments

Comments
 (0)