Merge branch 'ldap-pdu-cleanup', remote branch 'halostatue/ldap-pdu-cleanup' into ldap-pdu-cleanup

This commit is contained in:
Rory O'Connell 2010-04-15 17:05:40 -05:00
commit 0fce1048a5

View file

@ -1,4 +1,3 @@
#
# LDAP PDU support classes # LDAP PDU support classes
# #
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
@ -25,10 +24,28 @@
require 'ostruct' require 'ostruct'
module Net ##
class LdapPduError < StandardError; end # Defines the Protocol Data Unit (PDU) for LDAP. An LDAP PDU always looks
# like a BER SEQUENCE with at least two elements: an INTEGER message ID
# number and an application-specific SEQUENCE. Some LDAPv3 packets also
# include an optional third element, a sequence of "controls" (see RFC 2251
# section 4.1.12 for more information).
#
# The application-specific tag in the sequence tells us what kind of packet
# it is, and each kind has its own format, defined in RFC-1777.
#
# Observe that many clients (such as ldapsearch) do not necessarily enforce
# the expected application tags on received protocol packets. This
# implementation does interpret the RFC strictly in this regard, and it
# remains to be seen whether there are servers out there that will not work
# well with our approach.
#
# Currently, we only support controls on SearchResult.
class Net::LDAP::PDU
class Error < RuntimeError; end
class LdapPdu ##
# This message packet is a bind request.
BindRequest = 0 BindRequest = 0
BindResult = 1 BindResult = 1
UnbindRequest = 2 UnbindRequest = 2
@ -43,128 +60,120 @@ module Net
ExtendedRequest = 23 ExtendedRequest = 23
ExtendedResponse = 24 ExtendedResponse = 24
attr_reader :msg_id, :app_tag ##
attr_reader :search_dn, :search_attributes, :search_entry # The LDAP packet message ID.
attr_reader :search_referrals attr_reader :message_id
attr_reader :search_parameters, :bind_parameters alias_method :msg_id, :message_id
# An LDAP PDU always looks like a BerSequence with ##
# at least two elements: an integer (message-id number), and # The application protocol format tag.
# an application-specific sequence. attr_reader :app_tag
# Some LDAPv3 packets also include an optional
# third element, which is a sequence of "controls" attr_reader :search_entry
# (See RFC 2251, section 4.1.12). attr_reader :search_referrals
# The application-specific tag in the sequence tells attr_reader :search_parameters
# us what kind of packet it is, and each kind has its attr_reader :bind_parameters
# own format, defined in RFC-1777.
# Observe that many clients (such as ldapsearch) ##
# do not necessarily enforce the expected application # Returns RFC-2251 Controls if any.
# tags on received protocol packets. This implementation attr_reader :ldap_controls
# does interpret the RFC strictly in this regard, and alias_method :result_controls, :ldap_controls
# it remains to be seen whether there are servers out # Messy. Does this functionality belong somewhere else?
# there that will not work well with our approach.
# def initialize(ber_object)
# Added a controls-processor to SearchResult.
# Didn't add it everywhere because it just _feels_
# like it will need to be refactored.
#
def initialize ber_object
begin begin
@msg_id = ber_object[0].to_i @message_id = ber_object[0].to_i
# Modified 25Nov06. We want to "un-decorate" the ber-identifier # Grab the bottom five bits of the identifier so we know which type of
# of the incoming packet. Originally we did this by subtracting 0x60, # PDU this is.
# which ASSUMES the identifier is a constructed app-specific value. #
# But at least one value (UnbindRequest) is app-specific primitive. # This is safe enough in LDAP-land, but it is recommended that other
# So it makes more sense just to grab the bottom five bits. # approaches be taken for other protocols in the case that there's an
#@app_tag = ber_object[1].ber_identifier - 0x60 # app-specific tag that has both primitive and constructed forms.
@app_tag = ber_object[1].ber_identifier & 31 @app_tag = ber_object[1].ber_identifier & 0x1f
@ldap_controls = [] @ldap_controls = []
rescue rescue Exception => ex
# any error becomes a data-format error raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
raise LdapPduError.new( "ldap-pdu format error" )
end end
case @app_tag case @app_tag
when BindResult when BindResult
parse_bind_response ber_object[1] parse_bind_response(ber_object[1])
when SearchReturnedData when SearchReturnedData
parse_search_return ber_object[1] parse_search_return(ber_object[1])
when SearchResultReferral when SearchResultReferral
parse_search_referral ber_object[1] parse_search_referral(ber_object[1])
when SearchResult when SearchResult
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
parse_controls(ber_object[2]) if ber_object[2]
when ModifyResponse when ModifyResponse
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
when AddResponse when AddResponse
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
when DeleteResponse when DeleteResponse
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
when ModifyRDNResponse when ModifyRDNResponse
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
when SearchRequest when SearchRequest
parse_ldap_search_request ber_object[1] parse_ldap_search_request(ber_object[1])
when BindRequest when BindRequest
parse_bind_request ber_object[1] parse_bind_request(ber_object[1])
when UnbindRequest when UnbindRequest
parse_unbind_request ber_object[1] parse_unbind_request(ber_object[1])
when ExtendedResponse when ExtendedResponse
parse_ldap_result ber_object[1] parse_ldap_result(ber_object[1])
else else
raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" ) raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
end
end end
# Returns a hash which (usually) defines the members :resultCode, :errorMessage, and :matchedDN. parse_controls(ber_object[2]) if ber_object[2]
# These values come directly from an LDAP response packet returned by the remote peer. end
# See #result_code for a sugaring.
# ##
# Returns a hash which (usually) defines the members :resultCode,
# :errorMessage, and :matchedDN. These values come directly from an LDAP
# response packet returned by the remote peer. Also see #result_code.
def result def result
@ldap_result || {} @ldap_result || {}
end end
# This returns an LDAP result code taken from the PDU, ##
# but it will be nil if there wasn't a result code. # This returns an LDAP result code taken from the PDU, but it will be nil
# That can easily happen depending on the type of packet. # if there wasn't a result code. That can easily happen depending on the
# # type of packet.
def result_code code = :resultCode def result_code(code = :resultCode)
@ldap_result and @ldap_result[code] @ldap_result and @ldap_result[code]
end end
# Return RFC-2251 Controls if any. ##
# Messy. Does this functionality belong somewhere else?
def result_controls
@ldap_controls
end
# Return serverSaslCreds, which are only present in BindResponse packets. # Return serverSaslCreds, which are only present in BindResponse packets.
# Messy. Does this functionality belong somewhere else? #--
# We ought to refactor the accessors of this class before they get any kludgier. # Messy. Does this functionality belong somewhere else? We ought to
# refactor the accessors of this class before they get any kludgier.
def result_server_sasl_creds def result_server_sasl_creds
@ldap_result && @ldap_result[:serverSaslCreds] @ldap_result && @ldap_result[:serverSaslCreds]
end end
# parse_ldap_result def parse_ldap_result(sequence)
# sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
def parse_ldap_result sequence @ldap_result = {
sequence.length >= 3 or raise LdapPduError :resultCode => sequence[0],
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]} :matchedDN => sequence[1],
:errorMessage => sequence[2]
}
end end
private :parse_ldap_result private :parse_ldap_result
# A Bind Response may have an additional field, ID [7], serverSaslCreds, per RFC 2251 pgh 4.2.3. ##
# # A Bind Response may have an additional field, ID [7], serverSaslCreds,
def parse_bind_response sequence # per RFC 2251 pgh 4.2.3.
sequence.length >= 3 or raise LdapPduError def parse_bind_response(sequence)
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]} sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP Bind Response length."
parse_ldap_result(sequence)
@ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4 @ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
@ldap_result @ldap_result
end end
private :parse_bind_response private :parse_bind_response
# Definition from RFC 1777 (we're handling application-4 here) # Definition from RFC 1777 (we're handling application-4 here).
# #
# Search Response ::= # Search Response ::=
# CHOICE { # CHOICE {
@ -178,44 +187,44 @@ module Net
# resultCode [APPLICATION 5] LDAPResult # resultCode [APPLICATION 5] LDAPResult
# } # }
# #
# We concoct a search response that is a hash of the returned attribute values. # We concoct a search response that is a hash of the returned attribute
# values.
#
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES. # 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.
# #
# Provisionally removed obsolete search_attributes and search_dn, 04May06. # This is to make them more predictable for user programs, but it may not
# # be a good idea. Maybe this should be configurable.
def parse_search_return sequence def parse_search_return(sequence)
sequence.length >= 2 or raise LdapPduError sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
@search_entry = LDAP::Entry.new( sequence[0] ) @search_entry = LDAP::Entry.new(sequence[0])
sequence[1].each {|seq| sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
@search_entry[seq[0]] = seq[1]
}
end end
private :parse_search_return
# A search referral is a sequence of one or more LDAP URIs. ##
# Any number of search-referral replies can be returned by the server, interspersed # A search referral is a sequence of one or more LDAP URIs. Any number of
# with normal replies in any order. # search-referral replies can be returned by the server, interspersed with
# Until I can think of a better way to do this, we'll return the referrals as an array. # normal replies in any order.
# It'll be up to higher-level handlers to expose something reasonable to the client. #--
def parse_search_referral uris # Until I can think of a better way to do this, we'll return the referrals
# as an array. It'll be up to higher-level handlers to expose something
# reasonable to the client.
def parse_search_referral(uris)
@search_referrals = uris @search_referrals = uris
end end
private :parse_search_referral
##
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
# Octet String. If only two fields are given, the second one may be # Octet String. If only two fields are given, the second one may be either
# either criticality or data, since criticality has a default value. # criticality or data, since criticality has a default value. Someday we
# Someday we may want to come back here and add support for some of # may want to come back here and add support for some of more-widely used
# more-widely used controls. RFC-2696 is a good example. # controls. RFC-2696 is a good example.
# def parse_controls(sequence)
def parse_controls sequence
@ldap_controls = sequence.map do |control| @ldap_controls = sequence.map do |control|
o = OpenStruct.new o = OpenStruct.new
o.oid,o.criticality,o.value = control[0],control[1],control[2] o.oid, o.criticality, o.value = control[0], control[1], control[2]
if o.criticality and o.criticality.is_a?(String) if o.criticality and o.criticality.is_a?(String)
o.value = o.criticality o.value = o.criticality
o.criticality = false o.criticality = false
@ -223,35 +232,47 @@ module Net
o o
end end
end end
private :parse_controls private :parse_controls
# (provisional, must document) # (provisional, must document)
def parse_ldap_search_request sequence def parse_ldap_search_request(sequence)
s = OpenStruct.new s = OpenStruct.new
s.base_object, s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
s.scope, s.types_only, s.filter, s.attributes = sequence
s.deref_aliases,
s.size_limit,
s.time_limit,
s.types_only,
s.filter,
s.attributes = sequence
@search_parameters = s @search_parameters = s
end end
private :parse_ldap_search_request
# (provisional, must document) # (provisional, must document)
def parse_bind_request sequence def parse_bind_request sequence
s = OpenStruct.new s = OpenStruct.new
s.version, s.version, s.name, s.authentication = sequence
s.name,
s.authentication = sequence
@bind_parameters = s @bind_parameters = s
end end
private :parse_bind_request
# (provisional, must document) # (provisional, must document)
# UnbindRequest has no content so this is a no-op. # UnbindRequest has no content so this is a no-op.
def parse_unbind_request sequence def parse_unbind_request(sequence)
nil
end
private :parse_unbind_request
end
module Net
##
# Handle the renamed constants.
def self.const_missing(name) #:nodoc:
case name.to_s
when "LdapPdu"
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU instead."
Net::LDAP::PDU
when "LdapPduError"
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU::Error instead."
Net::LDAP::PDU::Error
else
super
end end
end end
end # module Net end # module Net