Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'ldap-pdu-cleanup', remote branch 'halostatue/ldap-pdu-c…

…leanup' into ldap-pdu-cleanup
  • Loading branch information...
commit 0fce1048a517b555ad94a50e2c028ef337f359c7 2 parents e981a11 + 6eaf87d
Rory O'Connell authored
Showing with 232 additions and 211 deletions.
  1. +232 −211 lib/net/ldap/pdu.rb
View
443 lib/net/ldap/pdu.rb
@@ -1,4 +1,3 @@
-#
# LDAP PDU support classes
#
#----------------------------------------------------------------------------
@@ -25,233 +24,255 @@
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
+
+ ##
+ # 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
- 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
+ ##
+ # The LDAP packet message ID.
+ attr_reader :message_id
+ alias_method :msg_id, :message_id
- attr_reader :msg_id, :app_tag
- attr_reader :search_dn, :search_attributes, :search_entry
- attr_reader :search_referrals
- attr_reader :search_parameters, :bind_parameters
+ ##
+ # The application protocol format tag.
+ attr_reader :app_tag
- # 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
+ attr_reader :search_entry
+ attr_reader :search_referrals
+ attr_reader :search_parameters
+ attr_reader :bind_parameters
- 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 RFC-2251 Controls if any.
+ attr_reader :ldap_controls
+ alias_method :result_controls, :ldap_controls
+ # Messy. Does this functionality belong somewhere else?
- # 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
+ 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
- # 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
+ 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
- # Return RFC-2251 Controls if any.
- # Messy. Does this functionality belong somewhere else?
- def result_controls
- @ldap_controls
- end
+ parse_controls(ber_object[2]) if ber_object[2]
+ 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
+ ##
+ # 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
- # 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
+ ##
+ # 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
- private :parse_ldap_result
+ ##
+ # 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
- # 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
+ 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
- private :parse_bind_response
+ ##
+ # 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.
- # 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
+ # 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
+ ##
+ # 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
+ ##
+ # 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
- 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_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
+ private :parse_bind_request
- # (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)
+ nil
+ end
+ private :parse_unbind_request
+end
- # (provisional, must document)
- # UnbindRequest has no content so this is a no-op.
- def parse_unbind_request sequence
- end
- 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 # module Net
+
Please sign in to comment.
Something went wrong with that request. Please try again.