# $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 < Exception; end class LdapPdu BindResult = 1 SearchReturnedData = 4 SearchResult = 5 ModifyResponse = 7 AddResponse = 9 DeleteResponse = 11 ModifyRDNResponse = 13 SearchResultReferral = 19 attr_reader :msg_id, :app_tag attr_reader :search_dn, :search_attributes, :search_entry attr_reader :search_referrals # # initialize # 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 @app_tag = ber_object[1].ber_identifier - 0x60 rescue # any error becomes a data-format error raise LdapPduError.new( "ldap-pdu format error" ) end case @app_tag when BindResult parse_ldap_result 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] else raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" ) end end # # result_code # 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 # # 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 # # parse_search_return # 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] ) #@search_dn = sequence[0] #@search_attributes = {} sequence[1].each {|seq| @search_entry[seq[0]] = seq[1] #@search_attributes[seq[0].downcase.intern] = 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 end end # module Net