@@ -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