@@ -335,8 +335,11 @@ class LdapError < StandardError; end
335335 68 => "Entry Already Exists"
336336 }
337337
338- module LdapControls
339- PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
338+ module LDAPControls
339+ PAGED_RESULTS = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696
340+ SORT_REQUEST = "1.2.840.113556.1.4.473"
341+ SORT_RESPONSE = "1.2.840.113556.1.4.474"
342+ DELETE_TREE = "1.2.840.113556.1.4.805"
340343 end
341344
342345 def self . result2string ( code ) #:nodoc:
@@ -629,11 +632,10 @@ def search(args = {})
629632 yield entry if block_given?
630633 }
631634 else
632- @result = 0
633635 begin
634636 conn = Net ::LDAP ::Connection . new ( :host => @host , :port => @port ,
635637 :encryption => @encryption )
636- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
638+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
637639 @result = conn . search ( args ) { |entry |
638640 result_set << entry if result_set
639641 yield entry if block_given?
@@ -645,9 +647,9 @@ def search(args = {})
645647 end
646648
647649 if return_result_set
648- @result == 0 ? result_set : nil
650+ ( ! @result . nil? && @result . result_code == 0 ) ? result_set : nil
649651 else
650- @result == 0
652+ @result
651653 end
652654 end
653655
@@ -721,7 +723,7 @@ def bind(auth = @auth)
721723 end
722724 end
723725
724- @result == 0
726+ @result
725727 end
726728
727729 # #bind_as is for testing authentication credentials.
@@ -816,14 +818,14 @@ def add(args)
816818 begin
817819 conn = Connection . new ( :host => @host , :port => @port ,
818820 :encryption => @encryption )
819- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
821+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
820822 @result = conn . add ( args )
821823 end
822824 ensure
823825 conn . close if conn
824826 end
825827 end
826- @result == 0
828+ @result
827829 end
828830
829831 # Modifies the attribute values of a particular entry on the LDAP
@@ -914,14 +916,15 @@ def modify(args)
914916 begin
915917 conn = Connection . new ( :host => @host , :port => @port ,
916918 :encryption => @encryption )
917- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
919+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
918920 @result = conn . modify ( args )
919921 end
920922 ensure
921923 conn . close if conn
922924 end
923925 end
924- @result == 0
926+
927+ @result
925928 end
926929
927930 # Add a value to an attribute. Takes the full DN of the entry to modify,
@@ -985,14 +988,14 @@ def rename(args)
985988 begin
986989 conn = Connection . new ( :host => @host , :port => @port ,
987990 :encryption => @encryption )
988- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
991+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
989992 @result = conn . rename ( args )
990993 end
991994 ensure
992995 conn . close if conn
993996 end
994997 end
995- @result == 0
998+ @result
996999 end
9971000 alias_method :modify_rdn , :rename
9981001
@@ -1013,16 +1016,29 @@ def delete(args)
10131016 begin
10141017 conn = Connection . new ( :host => @host , :port => @port ,
10151018 :encryption => @encryption )
1016- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) == 0
1019+ if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
10171020 @result = conn . delete ( args )
10181021 end
10191022 ensure
10201023 conn . close
10211024 end
10221025 end
1023- @result == 0
1026+ @result
10241027 end
10251028
1029+ # Delete an entry from the LDAP directory along with all subordinate entries.
1030+ # the regular delete method will fail to delete an entry if it has subordinate
1031+ # entries. This method sends an extra control code to tell the LDAP server
1032+ # to do a tree delete. ('1.2.840.113556.1.4.805')
1033+ #
1034+ # Returns True or False to indicate whether the delete succeeded. Extended
1035+ # status information is available by calling #get_operation_result.
1036+ #
1037+ # dn = "[email protected] , ou=people, dc=example, dc=com" 1038+ # ldap.delete_tree :dn => dn
1039+ def delete_tree ( args )
1040+ delete ( args . merge ( :control_codes => [ [ Net ::LDAP ::LDAPControls ::DELETE_TREE , true ] ] ) )
1041+ end
10261042 # This method is experimental and subject to change. Return the rootDSE
10271043 # record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
10281044 # the server doesn't return the record.
@@ -1093,7 +1109,7 @@ def search_subschema_entry
10931109 #++
10941110 def paged_searches_supported?
10951111 @server_caps ||= search_root_dse
1096- @server_caps [ :supportedcontrol ] . include? ( Net ::LDAP ::LdapControls :: PagedResults )
1112+ @server_caps [ :supportedcontrol ] . include? ( Net ::LDAP ::LDAPControls :: PAGED_RESULTS )
10971113 end
10981114end # class LDAP
10991115
@@ -1237,7 +1253,7 @@ def bind_simple(auth)
12371253
12381254 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) and pdu = Net ::LDAP ::PDU . new ( be ) ) or raise Net ::LDAP ::LdapError , "no bind result"
12391255
1240- pdu . result_code
1256+ pdu
12411257 end
12421258
12431259 #--
@@ -1275,7 +1291,7 @@ def bind_sasl(auth)
12751291 @conn . write request_pkt
12761292
12771293 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) and pdu = Net ::LDAP ::PDU . new ( be ) ) or raise Net ::LDAP ::LdapError , "no bind result"
1278- return pdu . result_code unless pdu . result_code == 14 # saslBindInProgress
1294+ return pdu unless pdu . result_code == 14 # saslBindInProgress
12791295 raise Net ::LDAP ::LdapError , "sasl-challenge overflow" if ( ( n += 1 ) > MaxSaslChallenges )
12801296
12811297 cred = chall . call ( pdu . result_server_sasl_creds )
@@ -1315,6 +1331,35 @@ def bind_gss_spnego(auth)
13151331 end
13161332 private :bind_gss_spnego
13171333
1334+
1335+ #--
1336+ # Allow the caller to specify a sort control
1337+ #
1338+ # The format of the sort control needs to be:
1339+ #
1340+ # :sort_control => ["cn"] # just a string
1341+ # or
1342+ # :sort_control => [["cn", "matchingRule", true]] #attribute, matchingRule, direction (true / false)
1343+ # or
1344+ # :sort_control => ["givenname","sn"] #multiple strings or arrays
1345+ #
1346+ def encode_sort_controls ( sort_definitions )
1347+ return sort_definitions unless sort_definitions
1348+
1349+ sort_control_values = sort_definitions . map do |control |
1350+ control = Array ( control ) # if there is only an attribute name as a string then infer the orderinrule and reverseorder
1351+ control [ 0 ] = String ( control [ 0 ] ) . to_ber ,
1352+ control [ 1 ] = String ( control [ 1 ] ) . to_ber ,
1353+ control [ 2 ] = ( control [ 2 ] == true ) . to_ber
1354+ control . to_ber_sequence
1355+ end
1356+ sort_control = [
1357+ Net ::LDAP ::LDAPControls ::SORT_REQUEST . to_ber ,
1358+ false . to_ber ,
1359+ sort_control_values . to_ber_sequence . to_s . to_ber
1360+ ] . to_ber_sequence
1361+ end
1362+
13181363 #--
13191364 # Alternate implementation, this yields each search entry to the caller as
13201365 # it are received.
@@ -1340,6 +1385,7 @@ def search(args = {})
13401385 scope = args [ :scope ] || Net ::LDAP ::SearchScope_WholeSubtree
13411386 raise Net ::LDAP ::LdapError , "invalid search scope" unless Net ::LDAP ::SearchScopes . include? ( scope )
13421387
1388+ sort_control = encode_sort_controls ( args . fetch ( :sort_controls ) { false } )
13431389 # An interesting value for the size limit would be close to A/D's
13441390 # built-in page limit of 1000 records, but openLDAP newer than version
13451391 # 2.2.0 chokes on anything bigger than 126. You get a silent error that
@@ -1361,7 +1407,7 @@ def search(args = {})
13611407 # to do a root-DSE record search and not do a paged search if the LDAP
13621408 # doesn't support it. Yuck.
13631409 rfc2696_cookie = [ 126 , "" ]
1364- result_code = 0
1410+ result_pdu = nil
13651411 n_results = 0
13661412
13671413 loop {
@@ -1390,17 +1436,18 @@ def search(args = {})
13901436 controls = [ ]
13911437 controls <<
13921438 [
1393- Net ::LDAP ::LdapControls :: PagedResults . to_ber ,
1439+ Net ::LDAP ::LDAPControls :: PAGED_RESULTS . to_ber ,
13941440 # Criticality MUST be false to interoperate with normal LDAPs.
13951441 false . to_ber ,
13961442 rfc2696_cookie . map { |v | v . to_ber } . to_ber_sequence . to_s . to_ber
13971443 ] . to_ber_sequence if paged_searches_supported
1444+ controls << sort_control if sort_control
13981445 controls = controls . empty? ? nil : controls . to_ber_contextspecific ( 0 )
13991446
14001447 pkt = [ next_msgid . to_ber , request , controls ] . compact . to_ber_sequence
14011448 @conn . write pkt
14021449
1403- result_code = 0
1450+ result_pdu = nil
14041451 controls = [ ]
14051452
14061453 while ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) && ( pdu = Net ::LDAP ::PDU . new ( be ) )
@@ -1417,7 +1464,7 @@ def search(args = {})
14171464 end
14181465 end
14191466 when 5 # search-result
1420- result_code = pdu . result_code
1467+ result_pdu = pdu
14211468 controls = pdu . result_controls
14221469 if return_referrals && result_code == 10
14231470 if block_given?
@@ -1443,9 +1490,9 @@ def search(args = {})
14431490 # of type OCTET STRING, covered in the default syntax supported by
14441491 # read_ber, so I guess we're ok.
14451492 more_pages = false
1446- if result_code == 0 and controls
1493+ if result_pdu . result_code == 0 and controls
14471494 controls . each do |c |
1448- if c . oid == Net ::LDAP ::LdapControls :: PagedResults
1495+ if c . oid == Net ::LDAP ::LDAPControls :: PAGED_RESULTS
14491496 # just in case some bogus server sends us more than 1 of these.
14501497 more_pages = false
14511498 if c . value and c . value . length > 0
@@ -1462,7 +1509,7 @@ def search(args = {})
14621509 break unless more_pages
14631510 } # loop
14641511
1465- result_code
1512+ result_pdu || OpenStruct . new ( :status => :failure , : result_code => 1 , :message => "Invalid search" )
14661513 end
14671514
14681515 MODIFY_OPERATIONS = { #:nodoc:
@@ -1502,7 +1549,8 @@ def modify(args)
15021549 @conn . write pkt
15031550
15041551 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) && ( pdu = Net ::LDAP ::PDU . new ( be ) ) && ( pdu . app_tag == 7 ) or raise Net ::LDAP ::LdapError , "response missing or invalid"
1505- pdu . result_code
1552+
1553+ pdu
15061554 end
15071555
15081556 #--
@@ -1523,8 +1571,12 @@ def add(args)
15231571 pkt = [ next_msgid . to_ber , request ] . to_ber_sequence
15241572 @conn . write pkt
15251573
1526- ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) && ( pdu = Net ::LDAP ::PDU . new ( be ) ) && ( pdu . app_tag == 9 ) or raise Net ::LDAP ::LdapError , "response missing or invalid"
1527- pdu . result_code
1574+ ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) &&
1575+ ( pdu = Net ::LDAP ::PDU . new ( be ) ) &&
1576+ ( pdu . app_tag == 9 ) or
1577+ raise Net ::LDAP ::LdapError , "response missing or invalid"
1578+
1579+ pdu
15281580 end
15291581
15301582 #--
@@ -1545,20 +1597,22 @@ def rename args
15451597 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) &&
15461598 ( pdu = Net ::LDAP ::PDU . new ( be ) ) && ( pdu . app_tag == 13 ) or
15471599 raise Net ::LDAP ::LdapError . new ( "response missing or invalid" )
1548- pdu . result_code
1600+
1601+ pdu
15491602 end
15501603
15511604 #--
15521605 # TODO, need to support a time limit, in case the server fails to respond.
15531606 #++
15541607 def delete ( args )
15551608 dn = args [ :dn ] or raise "Unable to delete empty DN"
1556-
1609+ controls = args . include? ( :control_codes ) ? args [ :control_codes ] . to_ber_control : nil #use nil so we can compact later
15571610 request = dn . to_s . to_ber_application_string ( 10 )
1558- pkt = [ next_msgid . to_ber , request ] . to_ber_sequence
1611+ pkt = [ next_msgid . to_ber , request , controls ] . compact . to_ber_sequence
15591612 @conn . write pkt
15601613
15611614 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) ) && ( pdu = Net ::LDAP ::PDU . new ( be ) ) && ( pdu . app_tag == 11 ) or raise Net ::LDAP ::LdapError , "response missing or invalid"
1562- pdu . result_code
1615+
1616+ pdu
15631617 end
15641618end # class Connection
0 commit comments