# $Id$ # # LDAP PDU 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 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 end # module Net