diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index feaf205..9d38702 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -23,6 +23,7 @@ require 'net/ldap/pdu' require 'net/ldap/filter' require 'net/ldap/dataset' require 'net/ldap/psw' +require 'net/ldap/entry' module Net @@ -372,17 +373,17 @@ module Net # Note that in the standalone case, we're permitting the caller # to modify the auth parms. # - def search args + def searchx args if @open_connection - @result = @open_connection.search( args ) {|values| - block_given? and yield( values ) + @result = @open_connection.searchx( args ) {|values| + yield( values ) if block_given? } else @result = 0 conn = Connection.new( :host => @host, :port => @port ) if (@result = conn.bind( args[:auth] || @auth )) == 0 - @result = conn.search( args ) {|values| - block_given? and yield( values ) + @result = conn.searchx( args ) {|values| + yield( values ) if block_given? } end conn.close @@ -391,6 +392,49 @@ module Net @result == 0 end + #-- + # This is a re-implementation of search that replaces the + # original one (now renamed searchx and possibly destined to go away). + # The difference is that we return a dataset (or nil) from the + # call, and pass _each entry_ as it is received from the server + # to the caler-supplied block. This will probably make things + # far faster as we can do useful work during the network latency + # of the search. The downside is that we have no access to the + # whole set while processing the blocks, so we can't do stuff + # like sort the DNs until after the call completes. + # It's also possible that this interacts badly with server timeouts. + # We'll have to ensure that something reasonable happens if + # the caller has processed half a result set when we throw a timeout + # error. + # Another important difference is that we return a result set from + # this method rather than a T/F indication. + # Since this can be very heavy-weight, we define an argument flag + # that the caller can set to suppress the return of a result set, + # if he's planning to process every entry as it comes from the server. + # + def search args + result_set = (args and args[:return_result] == false) ? nil : {} + + if @open_connection + @result = @open_connection.search( args ) {|entry| + result_set[entry.dn] = entry if result_set + yield( entry ) if block_given? + } + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port ) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.search( args ) {|entry| + result_set[entry.dn] = entry if result_set + yield( entry ) if block_given? + } + end + conn.close + end + + @result == 0 and result_set + end + # # bind # Bind and unbind. @@ -505,7 +549,7 @@ module Net raise LdapError.new( "no connection to server" ) end - block_given? and yield self + yield self if block_given? end @@ -552,15 +596,66 @@ module Net # # search + # Alternate implementation, this yields each search entry to the caller + # as it are received. # TODO, certain search parameters are hardcoded. # TODO, if we mis-parse the server results or the results are wrong, we can block # forever. That's because we keep reading results until we get a type-5 packet, # which might never come. We need to support the time-limit in the protocol. + #-- + # WARNING: this code substantially recapitulates the searchx method. # def search args search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) search_base = (args && args[:base]) || "dc=example,dc=com" search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} + + request = [ + search_base.to_ber, + 2.to_ber_enumerated, + 0.to_ber_enumerated, + 0.to_ber, + 0.to_ber, + false.to_ber, + search_filter.to_ber, + search_attributes.to_ber_sequence + ].to_ber_appsequence(3) + pkt = [next_msgid.to_ber, request].to_ber_sequence + @conn.write pkt + + result_code = 0 + + while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) + case pdu.app_tag + when 4 # search-data + yield( pdu.search_entry ) if block_given? + when 5 # search-result + result_code = pdu.result_code + break + else + raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" ) + end + end + + result_code + end + + + # + # searchx + # Original implementation, this doesn't return until all data have been + # received from the server. + # TODO, certain search parameters are hardcoded. + # TODO, if we mis-parse the server results or the results are wrong, we can block + # forever. That's because we keep reading results until we get a type-5 packet, + # which might never come. We need to support the time-limit in the protocol. + #-- + # WARNING: this code substantially recapitulates the search method. + # + def searchx args + search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) + search_base = (args && args[:base]) || "dc=example,dc=com" + search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} request = [ search_base.to_ber, 2.to_ber_enumerated, @@ -665,13 +760,3 @@ module Net end # module Net -#------------------------------------------------------ - -if __FILE__ == $0 - puts "No default action" -end - - - - - diff --git a/lib/net/ldap/entry.rb b/lib/net/ldap/entry.rb new file mode 100644 index 0000000..8abb3a3 --- /dev/null +++ b/lib/net/ldap/entry.rb @@ -0,0 +1,71 @@ +# $Id$ +# +# LDAP Entry (search-result) support classes +# +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. +# +# Gmail: garbagecat10 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#--------------------------------------------------------------------------- +# + + + + +module Net +class LDAP + + + class Entry + + def initialize dn = nil + @myhash = Hash.new {|k,v| k[v] = [] } + self[:dn] = [dn] + end + + + def []= name, value + sym = name.to_s.downcase.intern + @myhash[sym] = value + end + + def [] name + unless name.is_a?(Symbol) + name = name.to_s.downcase.intern + end + @myhash[name] + end + + def dn + self[:dn].shift + end + + def attribute_names + @myhash.keys + end + + + end # class Entry + + +end # class LDAP +end # module Net + + diff --git a/lib/net/ldap/pdu.rb b/lib/net/ldap/pdu.rb index 236fb75..5dadc0c 100644 --- a/lib/net/ldap/pdu.rb +++ b/lib/net/ldap/pdu.rb @@ -44,7 +44,7 @@ class LdapPdu ModifyRDNResponse = 13 attr_reader :msg_id, :app_tag - attr_reader :search_dn, :search_attributes + attr_reader :search_dn, :search_attributes, :search_entry # # initialize @@ -129,12 +129,17 @@ class LdapPdu # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES. # This is to make them more predictable for user programs, but it # may not be a good idea. Maybe this should be configurable. + # ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes, + # we also return @search_entry, which is an LDAP::Entry object. + # If that works out well, then we'll remove the first two. # def parse_search_return sequence sequence.length >= 2 or raise LdapPduError + @search_entry = LDAP::Entry.new( sequence[0] ) @search_dn = sequence[0] @search_attributes = {} sequence[1].each {|seq| + @search_entry[seq[0]] = seq[1] @search_attributes[seq[0].downcase.intern] = seq[1] } end