Cleaning up Net::LDAP::PDU (including object rename.
This commit is contained in:
parent
06ea324055
commit
6eaf87dc56
1 changed files with 250 additions and 229 deletions
|
@ -1,4 +1,3 @@
|
|||
#
|
||||
# LDAP PDU support classes
|
||||
#
|
||||
#----------------------------------------------------------------------------
|
||||
|
@ -25,233 +24,255 @@
|
|||
|
||||
require 'ostruct'
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
##
|
||||
# This message packet is a bind request.
|
||||
BindRequest = 0
|
||||
BindResult = 1
|
||||
UnbindRequest = 2
|
||||
SearchRequest = 3
|
||||
SearchReturnedData = 4
|
||||
SearchResult = 5
|
||||
ModifyResponse = 7
|
||||
AddResponse = 9
|
||||
DeleteResponse = 11
|
||||
ModifyRDNResponse = 13
|
||||
SearchResultReferral = 19
|
||||
ExtendedRequest = 23
|
||||
ExtendedResponse = 24
|
||||
|
||||
##
|
||||
# The LDAP packet message ID.
|
||||
attr_reader :message_id
|
||||
alias_method :msg_id, :message_id
|
||||
|
||||
##
|
||||
# The application protocol format tag.
|
||||
attr_reader :app_tag
|
||||
|
||||
attr_reader :search_entry
|
||||
attr_reader :search_referrals
|
||||
attr_reader :search_parameters
|
||||
attr_reader :bind_parameters
|
||||
|
||||
##
|
||||
# Returns RFC-2251 Controls if any.
|
||||
attr_reader :ldap_controls
|
||||
alias_method :result_controls, :ldap_controls
|
||||
# Messy. Does this functionality belong somewhere else?
|
||||
|
||||
def initialize(ber_object)
|
||||
begin
|
||||
@message_id = ber_object[0].to_i
|
||||
# Grab the bottom five bits of the identifier so we know which type of
|
||||
# PDU this is.
|
||||
#
|
||||
# This is safe enough in LDAP-land, but it is recommended that other
|
||||
# approaches be taken for other protocols in the case that there's an
|
||||
# app-specific tag that has both primitive and constructed forms.
|
||||
@app_tag = ber_object[1].ber_identifier & 0x1f
|
||||
@ldap_controls = []
|
||||
rescue Exception => ex
|
||||
raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
|
||||
end
|
||||
|
||||
case @app_tag
|
||||
when BindResult
|
||||
parse_bind_response(ber_object[1])
|
||||
when SearchReturnedData
|
||||
parse_search_return(ber_object[1])
|
||||
when SearchResultReferral
|
||||
parse_search_referral(ber_object[1])
|
||||
when SearchResult
|
||||
parse_ldap_result(ber_object[1])
|
||||
when ModifyResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when AddResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when DeleteResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when ModifyRDNResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when SearchRequest
|
||||
parse_ldap_search_request(ber_object[1])
|
||||
when BindRequest
|
||||
parse_bind_request(ber_object[1])
|
||||
when UnbindRequest
|
||||
parse_unbind_request(ber_object[1])
|
||||
when ExtendedResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
else
|
||||
raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
|
||||
end
|
||||
|
||||
parse_controls(ber_object[2]) if ber_object[2]
|
||||
end
|
||||
|
||||
##
|
||||
# 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
|
||||
@ldap_result || {}
|
||||
end
|
||||
|
||||
##
|
||||
# This returns an LDAP result code taken from the PDU, but it will be nil
|
||||
# if there wasn't a result code. That can easily happen depending on the
|
||||
# type of packet.
|
||||
def result_code(code = :resultCode)
|
||||
@ldap_result and @ldap_result[code]
|
||||
end
|
||||
|
||||
##
|
||||
# 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.
|
||||
def result_server_sasl_creds
|
||||
@ldap_result && @ldap_result[:serverSaslCreds]
|
||||
end
|
||||
|
||||
def parse_ldap_result(sequence)
|
||||
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
|
||||
@ldap_result = {
|
||||
:resultCode => sequence[0],
|
||||
:matchedDN => sequence[1],
|
||||
:errorMessage => sequence[2]
|
||||
}
|
||||
end
|
||||
private :parse_ldap_result
|
||||
|
||||
##
|
||||
# A Bind Response may have an additional field, ID [7], serverSaslCreds,
|
||||
# per RFC 2251 pgh 4.2.3.
|
||||
def parse_bind_response(sequence)
|
||||
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
|
||||
end
|
||||
private :parse_bind_response
|
||||
|
||||
# Definition from RFC 1777 (we're handling application-4 here).
|
||||
#
|
||||
# Search Response ::=
|
||||
# CHOICE {
|
||||
# entry [APPLICATION 4] SEQUENCE {
|
||||
# objectName LDAPDN,
|
||||
# attributes SEQUENCE OF SEQUENCE {
|
||||
# AttributeType,
|
||||
# SET OF AttributeValue
|
||||
# }
|
||||
# },
|
||||
# resultCode [APPLICATION 5] LDAPResult
|
||||
# }
|
||||
#
|
||||
# We concoct a search response that is a hash of the returned attribute
|
||||
# values.
|
||||
#
|
||||
# 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.
|
||||
def parse_search_return(sequence)
|
||||
sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
|
||||
@search_entry = LDAP::Entry.new(sequence[0])
|
||||
sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
|
||||
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 with
|
||||
# normal replies in any order.
|
||||
#--
|
||||
# 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
|
||||
end
|
||||
private :parse_search_referral
|
||||
|
||||
##
|
||||
# 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
|
||||
# Octet String. If only two fields are given, the second one may be either
|
||||
# criticality or data, since criticality has a default value. Someday we
|
||||
# may want to come back here and add support for some of more-widely used
|
||||
# controls. RFC-2696 is a good example.
|
||||
def parse_controls(sequence)
|
||||
@ldap_controls = sequence.map do |control|
|
||||
o = OpenStruct.new
|
||||
o.oid, o.criticality, o.value = control[0], control[1], control[2]
|
||||
if o.criticality and o.criticality.is_a?(String)
|
||||
o.value = o.criticality
|
||||
o.criticality = false
|
||||
end
|
||||
o
|
||||
end
|
||||
end
|
||||
private :parse_controls
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_ldap_search_request(sequence)
|
||||
s = OpenStruct.new
|
||||
s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
|
||||
s.types_only, s.filter, s.attributes = sequence
|
||||
@search_parameters = s
|
||||
end
|
||||
private :parse_ldap_search_request
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_bind_request sequence
|
||||
s = OpenStruct.new
|
||||
s.version, s.name, s.authentication = sequence
|
||||
@bind_parameters = s
|
||||
end
|
||||
private :parse_bind_request
|
||||
|
||||
# (provisional, must document)
|
||||
# UnbindRequest has no content so this is a no-op.
|
||||
def parse_unbind_request(sequence)
|
||||
nil
|
||||
end
|
||||
private :parse_unbind_request
|
||||
end
|
||||
|
||||
module Net
|
||||
class LdapPduError < StandardError; end
|
||||
|
||||
class LdapPdu
|
||||
BindRequest = 0
|
||||
BindResult = 1
|
||||
UnbindRequest = 2
|
||||
SearchRequest = 3
|
||||
SearchReturnedData = 4
|
||||
SearchResult = 5
|
||||
ModifyResponse = 7
|
||||
AddResponse = 9
|
||||
DeleteResponse = 11
|
||||
ModifyRDNResponse = 13
|
||||
SearchResultReferral = 19
|
||||
ExtendedRequest = 23
|
||||
ExtendedResponse = 24
|
||||
|
||||
attr_reader :msg_id, :app_tag
|
||||
attr_reader :search_dn, :search_attributes, :search_entry
|
||||
attr_reader :search_referrals
|
||||
attr_reader :search_parameters, :bind_parameters
|
||||
|
||||
# An LDAP PDU always looks like a BerSequence with
|
||||
# at least two elements: an integer (message-id number), and
|
||||
# an application-specific sequence.
|
||||
# Some LDAPv3 packets also include an optional
|
||||
# third element, which is a sequence of "controls"
|
||||
# (See RFC 2251, section 4.1.12).
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
@msg_id = ber_object[0].to_i
|
||||
# Modified 25Nov06. We want to "un-decorate" the ber-identifier
|
||||
# of the incoming packet. Originally we did this by subtracting 0x60,
|
||||
# which ASSUMES the identifier is a constructed app-specific value.
|
||||
# But at least one value (UnbindRequest) is app-specific primitive.
|
||||
# So it makes more sense just to grab the bottom five bits.
|
||||
#@app_tag = ber_object[1].ber_identifier - 0x60
|
||||
@app_tag = ber_object[1].ber_identifier & 31
|
||||
@ldap_controls = []
|
||||
rescue
|
||||
# any error becomes a data-format error
|
||||
raise LdapPduError.new( "ldap-pdu format error" )
|
||||
end
|
||||
|
||||
case @app_tag
|
||||
when BindResult
|
||||
parse_bind_response ber_object[1]
|
||||
when SearchReturnedData
|
||||
parse_search_return ber_object[1]
|
||||
when SearchResultReferral
|
||||
parse_search_referral ber_object[1]
|
||||
when SearchResult
|
||||
parse_ldap_result ber_object[1]
|
||||
parse_controls(ber_object[2]) if ber_object[2]
|
||||
when ModifyResponse
|
||||
parse_ldap_result ber_object[1]
|
||||
when AddResponse
|
||||
parse_ldap_result ber_object[1]
|
||||
when DeleteResponse
|
||||
parse_ldap_result ber_object[1]
|
||||
when ModifyRDNResponse
|
||||
parse_ldap_result ber_object[1]
|
||||
when SearchRequest
|
||||
parse_ldap_search_request ber_object[1]
|
||||
when BindRequest
|
||||
parse_bind_request ber_object[1]
|
||||
when UnbindRequest
|
||||
parse_unbind_request ber_object[1]
|
||||
when ExtendedResponse
|
||||
parse_ldap_result ber_object[1]
|
||||
else
|
||||
raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
|
||||
end
|
||||
end
|
||||
|
||||
# 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.
|
||||
# See #result_code for a sugaring.
|
||||
#
|
||||
def result
|
||||
@ldap_result || {}
|
||||
end
|
||||
|
||||
# This returns an LDAP result code taken from the PDU,
|
||||
# but it will be nil if there wasn't a result code.
|
||||
# That can easily happen depending on the type of packet.
|
||||
#
|
||||
def result_code code = :resultCode
|
||||
@ldap_result and @ldap_result[code]
|
||||
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.
|
||||
# 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
|
||||
@ldap_result && @ldap_result[:serverSaslCreds]
|
||||
end
|
||||
|
||||
# parse_ldap_result
|
||||
#
|
||||
def parse_ldap_result sequence
|
||||
sequence.length >= 3 or raise LdapPduError
|
||||
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
|
||||
end
|
||||
|
||||
private :parse_ldap_result
|
||||
|
||||
# A Bind Response may have an additional field, ID [7], serverSaslCreds, per RFC 2251 pgh 4.2.3.
|
||||
#
|
||||
def parse_bind_response sequence
|
||||
sequence.length >= 3 or raise LdapPduError
|
||||
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
|
||||
@ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
|
||||
@ldap_result
|
||||
end
|
||||
|
||||
private :parse_bind_response
|
||||
|
||||
# Definition from RFC 1777 (we're handling application-4 here)
|
||||
#
|
||||
# Search Response ::=
|
||||
# CHOICE {
|
||||
# entry [APPLICATION 4] SEQUENCE {
|
||||
# objectName LDAPDN,
|
||||
# attributes SEQUENCE OF SEQUENCE {
|
||||
# AttributeType,
|
||||
# SET OF AttributeValue
|
||||
# }
|
||||
# },
|
||||
# resultCode [APPLICATION 5] LDAPResult
|
||||
# }
|
||||
#
|
||||
# We concoct a search response that is a hash of the returned attribute values.
|
||||
# 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.
|
||||
#
|
||||
def parse_search_return sequence
|
||||
sequence.length >= 2 or raise LdapPduError
|
||||
@search_entry = LDAP::Entry.new( sequence[0] )
|
||||
sequence[1].each {|seq|
|
||||
@search_entry[seq[0]] = seq[1]
|
||||
}
|
||||
end
|
||||
|
||||
# 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
|
||||
# with normal replies in any order.
|
||||
# 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
|
||||
end
|
||||
|
||||
# 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
|
||||
# Octet String. If only two fields are given, the second one may be
|
||||
# either criticality or data, since criticality has a default value.
|
||||
# Someday we may want to come back here and add support for some of
|
||||
# more-widely used controls. RFC-2696 is a good example.
|
||||
#
|
||||
def parse_controls sequence
|
||||
@ldap_controls = sequence.map do |control|
|
||||
o = OpenStruct.new
|
||||
o.oid,o.criticality,o.value = control[0],control[1],control[2]
|
||||
if o.criticality and o.criticality.is_a?(String)
|
||||
o.value = o.criticality
|
||||
o.criticality = false
|
||||
end
|
||||
o
|
||||
end
|
||||
end
|
||||
|
||||
private :parse_controls
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_ldap_search_request sequence
|
||||
s = OpenStruct.new
|
||||
s.base_object,
|
||||
s.scope,
|
||||
s.deref_aliases,
|
||||
s.size_limit,
|
||||
s.time_limit,
|
||||
s.types_only,
|
||||
s.filter,
|
||||
s.attributes = sequence
|
||||
@search_parameters = s
|
||||
end
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_bind_request sequence
|
||||
s = OpenStruct.new
|
||||
s.version,
|
||||
s.name,
|
||||
s.authentication = sequence
|
||||
@bind_parameters = s
|
||||
end
|
||||
|
||||
# (provisional, must document)
|
||||
# UnbindRequest has no content so this is a no-op.
|
||||
def parse_unbind_request sequence
|
||||
end
|
||||
end
|
||||
##
|
||||
# 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 # module Net
|
||||
|
||||
|
|
Loading…
Reference in a new issue