@@ -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
0 commit comments