# Manipulation of LDAP schema data.
#
#--
# $Id: schema.rb,v 1.9 2006/02/08 23:15:17 ianmacd Exp $
#++

# The LDAP module encapsulates the various LDAP-related classes in their own
# namespace.
#
module LDAP

  # Retrieve and process information pertaining to LDAP schemas.
  #
  class Schema < Hash

    def initialize(entry)
      if( entry )
	entry.each{|key,vals|
	  self[key] = vals
	}
      end
    end

    # Return the list of values related to the schema component given in
    # +key+. See LDAP::Conn#schema for common values of +key+.
    #
    def names(key)
      self[key].collect{|val| val =~ /NAME\s+'([\w\d_-]+)'/; $1}
    end

    # Return the list of attributes in object class +oc+ that are of category
    # +at+. +at+ may be the string *MUST*, *MAY* or *SUP*.
    #
    def attr(oc,at)
      self['objectClasses'].each{|s|
	if( s =~ /NAME\s+'#{oc}'/ )
	  case s
	  when /#{at}\s+\(([\w\d_-\s\$]+)\)/i
	    return $1.split("$").collect{|attr| attr.strip}
	  when /#{at}\s+([\w\d_-]+)/i
	    return $1.split("$").collect{|attr| attr.strip}
	  end
	end
      }
      return nil
    end

    # Return the list of attributes that an entry with object class +oc+
    # _must_ possess.
    #
    def must(oc)
      attr(oc, "MUST")
    end

    # Return the list of attributes that an entry with object class +oc+
    # _may_ possess.
    #
    def may(oc)
      attr(oc, "MAY")
    end

    # Return the superior object class of object class +oc+.
    #
    def sup(oc)
      attr(oc, "SUP")
    end
  end

  class Conn

    # Fetch the schema data for the connection.
    #
    # If +base+ is given, it gives the base DN for the search. +attrs+, if
    # given, is an array of attributes that should be returned from the
    # server. The default list is *objectClasses*, *attributeTypes*,
    # *matchingRules*, *matchingRuleUse*, *dITStructureRules*,
    # *dITContentRules*, *nameForms* and *ldapSyntaxes*.
    #
    # +sec+ and +usec+ can be used to specify a time-out for the search in
    # seconds and microseconds, respectively.
    # 
    def schema(base = nil, attrs = nil, sec = 0, usec = 0)
      attrs ||= [
	'objectClasses',
	'attributeTypes',
	'matchingRules',
	'matchingRuleUse',
	'dITStructureRules',
	'dITContentRules',
	'nameForms',
	'ldapSyntaxes',
      ]
      base ||= root_dse(['subschemaSubentry'], sec, usec)[0]['subschemaSubentry'][0]
      base ||= 'cn=schema'
      ent = search2(base, LDAP_SCOPE_BASE, '(objectClass=subschema)',
		    attrs, false, sec, usec)
      return Schema.new(ent[0])
    end

    # Fetch the root DSE (DSA-specific Entry) for the connection. DSA stands
    # for Directory System Agent and simply refers to the LDAP server you are
    # using.
    #
    # +attrs+, if given, is an array of attributes that should be returned
    # from the server. The default list is *subschemaSubentry*,
    # *namingContexts*, *monitorContext*, *altServer*, *supportedControl*,
    # *supportedExtension*, *supportedFeatures*, *supportedSASLMechanisms*
    # and *supportedLDAPVersion*.
    #
    # +sec+ and +usec+ can be used to specify a time-out for the search in
    # seconds and microseconds, respectively.
    #
    def root_dse(attrs = nil, sec = 0, usec = 0)
      attrs ||= [
	'subschemaSubentry',
	'namingContexts',
	'monitorContext',
	'altServer',
	'supportedControl',
	'supportedExtension',
	'supportedFeatures',
	'supportedSASLMechanisms',
	'supportedLDAPVersion',
      ]

      entries = search2('', LDAP_SCOPE_BASE, '(objectClass=*)',
			attrs, false, sec, usec)
      return entries
    end
  end
end


syntax highlighted by Code2HTML, v. 0.9.1