@@ -292,6 +292,7 @@ class LdapError < Exception; end
292
292
2 => "Protocol Error" ,
293
293
3 => "Time Limit Exceeded" ,
294
294
4 => "Size Limit Exceeded" ,
295
+ 12 => "Unavailable crtical extension" ,
295
296
16 => "No Such Attribute" ,
296
297
17 => "Undefined Attribute Type" ,
297
298
20 => "Attribute or Value Exists" ,
@@ -308,6 +309,12 @@ class LdapError < Exception; end
308
309
68 => "Entry Already Exists"
309
310
}
310
311
312
+
313
+ module LdapControls
314
+ PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
315
+ end
316
+
317
+
311
318
#
312
319
# LDAP::result2string
313
320
#
@@ -869,6 +876,13 @@ def bind auth
869
876
#--
870
877
# WARNING: this code substantially recapitulates the searchx method.
871
878
#
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
+ #
872
886
def search args = { }
873
887
search_filter = ( args && args [ :filter ] ) || Filter . eq ( "objectclass" , "*" )
874
888
search_base = ( args && args [ :base ] ) || "dc=example,dc=com"
@@ -878,47 +892,73 @@ def search args = {}
878
892
scope = args [ :scope ] || Net ::LDAP ::SearchScope_WholeSubtree
879
893
raise LdapError . new ( "invalid search scope" ) unless SearchScopes . include? ( scope )
880
894
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.
908
896
result_code = 0
909
897
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
920
937
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
922
962
923
963
result_code
924
964
end
0 commit comments