Chris@0: # $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $ Chris@0: # Chris@0: # LDAP PDU support classes Chris@0: # Chris@0: # Chris@0: #---------------------------------------------------------------------------- Chris@0: # Chris@0: # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. Chris@0: # Chris@0: # Gmail: garbagecat10 Chris@0: # Chris@0: # This program is free software; you can redistribute it and/or modify Chris@0: # it under the terms of the GNU General Public License as published by Chris@0: # the Free Software Foundation; either version 2 of the License, or Chris@0: # (at your option) any later version. Chris@0: # Chris@0: # This program is distributed in the hope that it will be useful, Chris@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@0: # GNU General Public License for more details. Chris@0: # Chris@0: # You should have received a copy of the GNU General Public License Chris@0: # along with this program; if not, write to the Free Software Chris@0: # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Chris@0: # Chris@0: #--------------------------------------------------------------------------- Chris@0: # Chris@0: Chris@0: Chris@0: Chris@0: module Net Chris@0: Chris@0: Chris@0: class LdapPduError < Exception; end Chris@0: Chris@0: Chris@0: class LdapPdu Chris@0: Chris@0: BindResult = 1 Chris@0: SearchReturnedData = 4 Chris@0: SearchResult = 5 Chris@0: ModifyResponse = 7 Chris@0: AddResponse = 9 Chris@0: DeleteResponse = 11 Chris@0: ModifyRDNResponse = 13 Chris@0: SearchResultReferral = 19 Chris@0: Chris@0: attr_reader :msg_id, :app_tag Chris@0: attr_reader :search_dn, :search_attributes, :search_entry Chris@0: attr_reader :search_referrals Chris@0: Chris@0: # Chris@0: # initialize Chris@0: # An LDAP PDU always looks like a BerSequence with Chris@0: # at least two elements: an integer (message-id number), and Chris@0: # an application-specific sequence. Chris@0: # Some LDAPv3 packets also include an optional Chris@0: # third element, which is a sequence of "controls" Chris@0: # (See RFC 2251, section 4.1.12). Chris@0: # The application-specific tag in the sequence tells Chris@0: # us what kind of packet it is, and each kind has its Chris@0: # own format, defined in RFC-1777. Chris@0: # Observe that many clients (such as ldapsearch) Chris@0: # do not necessarily enforce the expected application Chris@0: # tags on received protocol packets. This implementation Chris@0: # does interpret the RFC strictly in this regard, and Chris@0: # it remains to be seen whether there are servers out Chris@0: # there that will not work well with our approach. Chris@0: # Chris@0: # Added a controls-processor to SearchResult. Chris@0: # Didn't add it everywhere because it just _feels_ Chris@0: # like it will need to be refactored. Chris@0: # Chris@0: def initialize ber_object Chris@0: begin Chris@0: @msg_id = ber_object[0].to_i Chris@0: @app_tag = ber_object[1].ber_identifier - 0x60 Chris@0: rescue Chris@0: # any error becomes a data-format error Chris@0: raise LdapPduError.new( "ldap-pdu format error" ) Chris@0: end Chris@0: Chris@0: case @app_tag Chris@0: when BindResult Chris@0: parse_ldap_result ber_object[1] Chris@0: when SearchReturnedData Chris@0: parse_search_return ber_object[1] Chris@0: when SearchResultReferral Chris@0: parse_search_referral ber_object[1] Chris@0: when SearchResult Chris@0: parse_ldap_result ber_object[1] Chris@0: parse_controls(ber_object[2]) if ber_object[2] Chris@0: when ModifyResponse Chris@0: parse_ldap_result ber_object[1] Chris@0: when AddResponse Chris@0: parse_ldap_result ber_object[1] Chris@0: when DeleteResponse Chris@0: parse_ldap_result ber_object[1] Chris@0: when ModifyRDNResponse Chris@0: parse_ldap_result ber_object[1] Chris@0: else Chris@0: raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" ) Chris@0: end Chris@0: end Chris@0: Chris@0: # Chris@0: # result_code Chris@0: # This returns an LDAP result code taken from the PDU, Chris@0: # but it will be nil if there wasn't a result code. Chris@0: # That can easily happen depending on the type of packet. Chris@0: # Chris@0: def result_code code = :resultCode Chris@0: @ldap_result and @ldap_result[code] Chris@0: end Chris@0: Chris@0: # Return RFC-2251 Controls if any. Chris@0: # Messy. Does this functionality belong somewhere else? Chris@0: def result_controls Chris@0: @ldap_controls || [] Chris@0: end Chris@0: Chris@0: Chris@0: # Chris@0: # parse_ldap_result Chris@0: # Chris@0: def parse_ldap_result sequence Chris@0: sequence.length >= 3 or raise LdapPduError Chris@0: @ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]} Chris@0: end Chris@0: private :parse_ldap_result Chris@0: Chris@0: # Chris@0: # parse_search_return Chris@0: # Definition from RFC 1777 (we're handling application-4 here) Chris@0: # Chris@0: # Search Response ::= Chris@0: # CHOICE { Chris@0: # entry [APPLICATION 4] SEQUENCE { Chris@0: # objectName LDAPDN, Chris@0: # attributes SEQUENCE OF SEQUENCE { Chris@0: # AttributeType, Chris@0: # SET OF AttributeValue Chris@0: # } Chris@0: # }, Chris@0: # resultCode [APPLICATION 5] LDAPResult Chris@0: # } Chris@0: # Chris@0: # We concoct a search response that is a hash of the returned attribute values. Chris@0: # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES. Chris@0: # This is to make them more predictable for user programs, but it Chris@0: # may not be a good idea. Maybe this should be configurable. Chris@0: # ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes, Chris@0: # we also return @search_entry, which is an LDAP::Entry object. Chris@0: # If that works out well, then we'll remove the first two. Chris@0: # Chris@0: # Provisionally removed obsolete search_attributes and search_dn, 04May06. Chris@0: # Chris@0: def parse_search_return sequence Chris@0: sequence.length >= 2 or raise LdapPduError Chris@0: @search_entry = LDAP::Entry.new( sequence[0] ) Chris@0: #@search_dn = sequence[0] Chris@0: #@search_attributes = {} Chris@0: sequence[1].each {|seq| Chris@0: @search_entry[seq[0]] = seq[1] Chris@0: #@search_attributes[seq[0].downcase.intern] = seq[1] Chris@0: } Chris@0: end Chris@0: Chris@0: # Chris@0: # A search referral is a sequence of one or more LDAP URIs. Chris@0: # Any number of search-referral replies can be returned by the server, interspersed Chris@0: # with normal replies in any order. Chris@0: # Until I can think of a better way to do this, we'll return the referrals as an array. Chris@0: # It'll be up to higher-level handlers to expose something reasonable to the client. Chris@0: def parse_search_referral uris Chris@0: @search_referrals = uris Chris@0: end Chris@0: Chris@0: Chris@0: # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting Chris@0: # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL Chris@0: # Octet String. If only two fields are given, the second one may be Chris@0: # either criticality or data, since criticality has a default value. Chris@0: # Someday we may want to come back here and add support for some of Chris@0: # more-widely used controls. RFC-2696 is a good example. Chris@0: # Chris@0: def parse_controls sequence Chris@0: @ldap_controls = sequence.map do |control| Chris@0: o = OpenStruct.new Chris@0: o.oid,o.criticality,o.value = control[0],control[1],control[2] Chris@0: if o.criticality and o.criticality.is_a?(String) Chris@0: o.value = o.criticality Chris@0: o.criticality = false Chris@0: end Chris@0: o Chris@0: end Chris@0: end Chris@0: private :parse_controls Chris@0: Chris@0: Chris@0: end Chris@0: Chris@0: Chris@0: end # module Net Chris@0: