diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-01-02 08:48:40 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-01-02 08:48:40 +0000 |
commit | f50544bb156de9c9b44b6c7806964633337fd475 (patch) | |
tree | 69887bdd4967ea7d22b08d508a226a6929b946ac /vendor/plugins/ruby-net-ldap-0.0.4/lib | |
parent | e3becc7c3c53aff0566cd862c13b0bd032da1169 (diff) | |
download | redmine-f50544bb156de9c9b44b6c7806964633337fd475.tar.gz redmine-f50544bb156de9c9b44b6c7806964633337fd475.zip |
addded ruby-net-ldap (0.0.4) dependency in vendor/pluggin
git-svn-id: http://redmine.rubyforge.org/svn/trunk@134 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'vendor/plugins/ruby-net-ldap-0.0.4/lib')
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ber.rb | 294 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap.rb | 1311 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/dataset.rb | 108 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb | 165 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/filter.rb | 387 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb | 205 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb | 64 | ||||
-rw-r--r-- | vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldif.rb | 39 |
8 files changed, 2573 insertions, 0 deletions
diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ber.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ber.rb new file mode 100644 index 000000000..e76100656 --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ber.rb @@ -0,0 +1,294 @@ +# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $ +# +# NET::BER +# Mixes ASN.1/BER convenience methods into several standard classes. +# Also provides BER parsing functionality. +# +#---------------------------------------------------------------------------- +# +# 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 + + module BER + + class BerError < Exception; end + + + # This module is for mixing into IO and IO-like objects. + module BERParser + + # The order of these follows the class-codes in BER. + # Maybe this should have been a hash. + TagClasses = [:universal, :application, :context_specific, :private] + + BuiltinSyntax = { + :universal => { + :primitive => { + 1 => :boolean, + 2 => :integer, + 4 => :string, + 10 => :integer, + }, + :constructed => { + 16 => :array, + 17 => :array + } + } + } + + # + # read_ber + # TODO: clean this up so it works properly with partial + # packets coming from streams that don't block when + # we ask for more data (like StringIOs). At it is, + # this can throw TypeErrors and other nasties. + # + def read_ber syntax=nil + return nil if eof? + + id = getc # don't trash this value, we'll use it later + tag = id & 31 + tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" ) + tagclass = TagClasses[ id >> 6 ] + encoding = (id & 0x20 != 0) ? :constructed : :primitive + + n = getc + lengthlength,contentlength = if n <= 127 + [1,n] + else + j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc} + [1 + (n & 127), j] + end + + newobj = read contentlength + + objtype = nil + [syntax, BuiltinSyntax].each {|syn| + if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag] + objtype = ot[tag] + break + end + } + + obj = case objtype + when :boolean + newobj != "\000" + when :string + (newobj || "").dup + when :integer + j = 0 + newobj.each_byte {|b| j = (j << 8) + b} + j + when :array + seq = [] + sio = StringIO.new( newobj || "" ) + # Interpret the subobject, but note how the loop + # is built: nil ends the loop, but false (a valid + # BER value) does not! + while (e = sio.read_ber(syntax)) != nil + seq << e + end + seq + else + raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" ) + end + + # Add the identifier bits into the object if it's a String or an Array. + # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway. + obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end" + obj + + end + + end # module BERParser + end # module BER + +end # module Net + + +class IO + include Net::BER::BERParser +end + +require "stringio" +class StringIO + include Net::BER::BERParser +end + +begin + require 'openssl' + class OpenSSL::SSL::SSLSocket + include Net::BER::BERParser + end +rescue LoadError +# Ignore LoadError. +# DON'T ignore NameError, which means the SSLSocket class +# is somehow unavailable on this implementation of Ruby's openssl. +# This may be WRONG, however, because we don't yet know how Ruby's +# openssl behaves on machines with no OpenSSL library. I suppose +# it's possible they do not fail to require 'openssl' but do not +# create the classes. So this code is provisional. +# Also, you might think that OpenSSL::SSL::SSLSocket inherits from +# IO so we'd pick it up above. But you'd be wrong. +end + +class String + def read_ber syntax=nil + StringIO.new(self).read_ber(syntax) + end +end + + + +#---------------------------------------------- + + +class FalseClass + # + # to_ber + # + def to_ber + "\001\001\000" + end +end + + +class TrueClass + # + # to_ber + # + def to_ber + "\001\001\001" + end +end + + + +class Fixnum + # + # to_ber + # + def to_ber + i = [self].pack('w') + [2, i.length].pack("CC") + i + end + + # + # to_ber_enumerated + # + def to_ber_enumerated + i = [self].pack('w') + [10, i.length].pack("CC") + i + end + + # + # to_ber_length_encoding + # + def to_ber_length_encoding + if self <= 127 + [self].pack('C') + else + i = [self].pack('N').sub(/^[\0]+/,"") + [0x80 + i.length].pack('C') + i + end + end + +end # class Fixnum + + +class Bignum + + def to_ber + i = [self].pack('w') + i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" ) + [2, i.length].pack("CC") + i + end + +end + + + +class String + # + # to_ber + # A universal octet-string is tag number 4, + # but others are possible depending on the context, so we + # let the caller give us one. + # The preferred way to do this in user code is via to_ber_application_sring + # and to_ber_contextspecific. + # + def to_ber code = 4 + [code].pack('C') + length.to_ber_length_encoding + self + end + + # + # to_ber_application_string + # + def to_ber_application_string code + to_ber( 0x40 + code ) + end + + # + # to_ber_contextspecific + # + def to_ber_contextspecific code + to_ber( 0x80 + code ) + end + +end # class String + + + +class Array + # + # to_ber_appsequence + # An application-specific sequence usually gets assigned + # a tag that is meaningful to the particular protocol being used. + # This is different from the universal sequence, which usually + # gets a tag value of 16. + # Now here's an interesting thing: We're adding the X.690 + # "application constructed" code at the top of the tag byte (0x60), + # but some clients, notably ldapsearch, send "context-specific + # constructed" (0xA0). The latter would appear to violate RFC-1777, + # but what do I know? We may need to change this. + # + + def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end + def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end + def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end + def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end + def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end + + private + def to_ber_seq_internal code + s = self.to_s + [code].pack('C') + s.length.to_ber_length_encoding + s + end + +end # class Array + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap.rb new file mode 100644 index 000000000..d741e722b --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap.rb @@ -0,0 +1,1311 @@ +# $Id: ldap.rb 154 2006-08-15 09:35:43Z blackhedd $ +# +# Net::LDAP for Ruby +# +# +# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. +# +# Written and maintained by Francis Cianfrocca, gmail: garbagecat10. +# +# This program is free software. +# You may re-distribute and/or modify this program under the same terms +# as Ruby itself: Ruby Distribution License or GNU General Public License. +# +# +# See Net::LDAP for documentation and usage samples. +# + + +require 'socket' +require 'ostruct' + +begin + require 'openssl' + $net_ldap_openssl_available = true +rescue LoadError +end + +require 'net/ber' +require 'net/ldap/pdu' +require 'net/ldap/filter' +require 'net/ldap/dataset' +require 'net/ldap/psw' +require 'net/ldap/entry' + + +module Net + + + # == Net::LDAP + # + # This library provides a pure-Ruby implementation of the + # LDAP client protocol, per RFC-2251. + # It can be used to access any server which implements the + # LDAP protocol. + # + # Net::LDAP is intended to provide full LDAP functionality + # while hiding the more arcane aspects + # the LDAP protocol itself, and thus presenting as Ruby-like + # a programming interface as possible. + # + # == Quick-start for the Impatient + # === Quick Example of a user-authentication against an LDAP directory: + # + # require 'rubygems' + # require 'net/ldap' + # + # ldap = Net::LDAP.new + # ldap.host = your_server_ip_address + # ldap.port = 389 + # ldap.auth "joe_user", "opensesame" + # if ldap.bind + # # authentication succeeded + # else + # # authentication failed + # end + # + # + # === Quick Example of a search against an LDAP directory: + # + # require 'rubygems' + # require 'net/ldap' + # + # ldap = Net::LDAP.new :host => server_ip_address, + # :port => 389, + # :auth => { + # :method => :simple, + # :username => "cn=manager,dc=example,dc=com", + # :password => "opensesame" + # } + # + # filter = Net::LDAP::Filter.eq( "cn", "George*" ) + # treebase = "dc=example,dc=com" + # + # ldap.search( :base => treebase, :filter => filter ) do |entry| + # puts "DN: #{entry.dn}" + # entry.each do |attribute, values| + # puts " #{attribute}:" + # values.each do |value| + # puts " --->#{value}" + # end + # end + # end + # + # p ldap.get_operation_result + # + # + # == A Brief Introduction to LDAP + # + # We're going to provide a quick, informal introduction to LDAP + # terminology and + # typical operations. If you're comfortable with this material, skip + # ahead to "How to use Net::LDAP." If you want a more rigorous treatment + # of this material, we recommend you start with the various IETF and ITU + # standards that relate to LDAP. + # + # === Entities + # LDAP is an Internet-standard protocol used to access directory servers. + # The basic search unit is the <i>entity,</i> which corresponds to + # a person or other domain-specific object. + # A directory service which supports the LDAP protocol typically + # stores information about a number of entities. + # + # === Principals + # LDAP servers are typically used to access information about people, + # but also very often about such items as printers, computers, and other + # resources. To reflect this, LDAP uses the term <i>entity,</i> or less + # commonly, <i>principal,</i> to denote its basic data-storage unit. + # + # + # === Distinguished Names + # In LDAP's view of the world, + # an entity is uniquely identified by a globally-unique text string + # called a <i>Distinguished Name,</i> originally defined in the X.400 + # standards from which LDAP is ultimately derived. + # Much like a DNS hostname, a DN is a "flattened" text representation + # of a string of tree nodes. Also like DNS (and unlike Java package + # names), a DN expresses a chain of tree-nodes written from left to right + # in order from the most-resolved node to the most-general one. + # + # If you know the DN of a person or other entity, then you can query + # an LDAP-enabled directory for information (attributes) about the entity. + # Alternatively, you can query the directory for a list of DNs matching + # a set of criteria that you supply. + # + # === Attributes + # + # In the LDAP view of the world, a DN uniquely identifies an entity. + # Information about the entity is stored as a set of <i>Attributes.</i> + # An attribute is a text string which is associated with zero or more + # values. Most LDAP-enabled directories store a well-standardized + # range of attributes, and constrain their values according to standard + # rules. + # + # A good example of an attribute is <tt>sn,</tt> which stands for "Surname." + # This attribute is generally used to store a person's surname, or last name. + # Most directories enforce the standard convention that + # an entity's <tt>sn</tt> attribute have <i>exactly one</i> value. In LDAP + # jargon, that means that <tt>sn</tt> must be <i>present</i> and + # <i>single-valued.</i> + # + # Another attribute is <tt>mail,</tt> which is used to store email addresses. + # (No, there is no attribute called "email," perhaps because X.400 terminology + # predates the invention of the term <i>email.</i>) <tt>mail</tt> differs + # from <tt>sn</tt> in that most directories permit any number of values for the + # <tt>mail</tt> attribute, including zero. + # + # + # === Tree-Base + # We said above that X.400 Distinguished Names are <i>globally unique.</i> + # In a manner reminiscent of DNS, LDAP supposes that each directory server + # contains authoritative attribute data for a set of DNs corresponding + # to a specific sub-tree of the (notional) global directory tree. + # This subtree is generally configured into a directory server when it is + # created. It matters for this discussion because most servers will not + # allow you to query them unless you specify a correct tree-base. + # + # Let's say you work for the engineering department of Big Company, Inc., + # whose internet domain is bigcompany.com. You may find that your departmental + # directory is stored in a server with a defined tree-base of + # ou=engineering,dc=bigcompany,dc=com + # You will need to supply this string as the <i>tree-base</i> when querying this + # directory. (Ou is a very old X.400 term meaning "organizational unit." + # Dc is a more recent term meaning "domain component.") + # + # === LDAP Versions + # (stub, discuss v2 and v3) + # + # === LDAP Operations + # The essential operations are: #bind, #search, #add, #modify, #delete, and #rename. + # ==== Bind + # #bind supplies a user's authentication credentials to a server, which in turn verifies + # or rejects them. There is a range of possibilities for credentials, but most directories + # support a simple username and password authentication. + # + # Taken by itself, #bind can be used to authenticate a user against information + # stored in a directory, for example to permit or deny access to some other resource. + # In terms of the other LDAP operations, most directories require a successful #bind to + # be performed before the other operations will be permitted. Some servers permit certain + # operations to be performed with an "anonymous" binding, meaning that no credentials are + # presented by the user. (We're glossing over a lot of platform-specific detail here.) + # + # ==== Search + # Calling #search against the directory involves specifying a treebase, a set of <i>search filters,</i> + # and a list of attribute values. + # The filters specify ranges of possible values for particular attributes. Multiple + # filters can be joined together with AND, OR, and NOT operators. + # A server will respond to a #search by returning a list of matching DNs together with a + # set of attribute values for each entity, depending on what attributes the search requested. + # + # ==== Add + # #add specifies a new DN and an initial set of attribute values. If the operation + # succeeds, a new entity with the corresponding DN and attributes is added to the directory. + # + # ==== Modify + # #modify specifies an entity DN, and a list of attribute operations. #modify is used to change + # the attribute values stored in the directory for a particular entity. + # #modify may add or delete attributes (which are lists of values) or it change attributes by + # adding to or deleting from their values. + # Net::LDAP provides three easier methods to modify an entry's attribute values: + # #add_attribute, #replace_attribute, and #delete_attribute. + # + # ==== Delete + # #delete specifies an entity DN. If it succeeds, the entity and all its attributes + # is removed from the directory. + # + # ==== Rename (or Modify RDN) + # #rename (or #modify_rdn) is an operation added to version 3 of the LDAP protocol. It responds to + # the often-arising need to change the DN of an entity without discarding its attribute values. + # In earlier LDAP versions, the only way to do this was to delete the whole entity and add it + # again with a different DN. + # + # #rename works by taking an "old" DN (the one to change) and a "new RDN," which is the left-most + # part of the DN string. If successful, #rename changes the entity DN so that its left-most + # node corresponds to the new RDN given in the request. (RDN, or "relative distinguished name," + # denotes a single tree-node as expressed in a DN, which is a chain of tree nodes.) + # + # == How to use Net::LDAP + # + # To access Net::LDAP functionality in your Ruby programs, start by requiring + # the library: + # + # require 'net/ldap' + # + # If you installed the Gem version of Net::LDAP, and depending on your version of + # Ruby and rubygems, you _may_ also need to require rubygems explicitly: + # + # require 'rubygems' + # require 'net/ldap' + # + # Most operations with Net::LDAP start by instantiating a Net::LDAP object. + # The constructor for this object takes arguments specifying the network location + # (address and port) of the LDAP server, and also the binding (authentication) + # credentials, typically a username and password. + # Given an object of class Net:LDAP, you can then perform LDAP operations by calling + # instance methods on the object. These are documented with usage examples below. + # + # The Net::LDAP library is designed to be very disciplined about how it makes network + # connections to servers. This is different from many of the standard native-code + # libraries that are provided on most platforms, which share bloodlines with the + # original Netscape/Michigan LDAP client implementations. These libraries sought to + # insulate user code from the workings of the network. This is a good idea of course, + # but the practical effect has been confusing and many difficult bugs have been caused + # by the opacity of the native libraries, and their variable behavior across platforms. + # + # In general, Net::LDAP instance methods which invoke server operations make a connection + # to the server when the method is called. They execute the operation (typically binding first) + # and then disconnect from the server. The exception is Net::LDAP#open, which makes a connection + # to the server and then keeps it open while it executes a user-supplied block. Net::LDAP#open + # closes the connection on completion of the block. + # + + class LDAP + + class LdapError < Exception; end + + VERSION = "0.0.4" + + + SearchScope_BaseObject = 0 + SearchScope_SingleLevel = 1 + SearchScope_WholeSubtree = 2 + SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel, SearchScope_WholeSubtree] + + AsnSyntax = { + :application => { + :constructed => { + 0 => :array, # BindRequest + 1 => :array, # BindResponse + 2 => :array, # UnbindRequest + 3 => :array, # SearchRequest + 4 => :array, # SearchData + 5 => :array, # SearchResult + 6 => :array, # ModifyRequest + 7 => :array, # ModifyResponse + 8 => :array, # AddRequest + 9 => :array, # AddResponse + 10 => :array, # DelRequest + 11 => :array, # DelResponse + 12 => :array, # ModifyRdnRequest + 13 => :array, # ModifyRdnResponse + 14 => :array, # CompareRequest + 15 => :array, # CompareResponse + 16 => :array, # AbandonRequest + 19 => :array, # SearchResultReferral + 24 => :array, # Unsolicited Notification + } + }, + :context_specific => { + :primitive => { + 0 => :string, # password + 1 => :string, # Kerberos v4 + 2 => :string, # Kerberos v5 + }, + :constructed => { + 0 => :array, # RFC-2251 Control + 3 => :array, # Seach referral + } + } + } + + DefaultHost = "127.0.0.1" + DefaultPort = 389 + DefaultAuth = {:method => :anonymous} + DefaultTreebase = "dc=com" + + + ResultStrings = { + 0 => "Success", + 1 => "Operations Error", + 2 => "Protocol Error", + 3 => "Time Limit Exceeded", + 4 => "Size Limit Exceeded", + 12 => "Unavailable crtical extension", + 16 => "No Such Attribute", + 17 => "Undefined Attribute Type", + 20 => "Attribute or Value Exists", + 32 => "No Such Object", + 34 => "Invalid DN Syntax", + 48 => "Invalid DN Syntax", + 48 => "Inappropriate Authentication", + 49 => "Invalid Credentials", + 50 => "Insufficient Access Rights", + 51 => "Busy", + 52 => "Unavailable", + 53 => "Unwilling to perform", + 65 => "Object Class Violation", + 68 => "Entry Already Exists" + } + + + module LdapControls + PagedResults = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696 + end + + + # + # LDAP::result2string + # + def LDAP::result2string code # :nodoc: + ResultStrings[code] || "unknown result (#{code})" + end + + + attr_accessor :host, :port, :base + + + # Instantiate an object of type Net::LDAP to perform directory operations. + # This constructor takes a Hash containing arguments, all of which are either optional or may be specified later with other methods as described below. The following arguments + # are supported: + # * :host => the LDAP server's IP-address (default 127.0.0.1) + # * :port => the LDAP server's TCP port (default 389) + # * :auth => a Hash containing authorization parameters. Currently supported values include: + # {:method => :anonymous} and + # {:method => :simple, :username => your_user_name, :password => your_password } + # The password parameter may be a Proc that returns a String. + # * :base => a default treebase parameter for searches performed against the LDAP server. If you don't give this value, then each call to #search must specify a treebase parameter. If you do give this value, then it will be used in subsequent calls to #search that do not specify a treebase. If you give a treebase value in any particular call to #search, that value will override any treebase value you give here. + # * :encryption => specifies the encryption to be used in communicating with the LDAP server. The value is either a Hash containing additional parameters, or the Symbol :simple_tls, which is equivalent to specifying the Hash {:method => :simple_tls}. There is a fairly large range of potential values that may be given for this parameter. See #encryption for details. + # + # Instantiating a Net::LDAP object does <i>not</i> result in network traffic to + # the LDAP server. It simply stores the connection and binding parameters in the + # object. + # + def initialize args = {} + @host = args[:host] || DefaultHost + @port = args[:port] || DefaultPort + @verbose = false # Make this configurable with a switch on the class. + @auth = args[:auth] || DefaultAuth + @base = args[:base] || DefaultTreebase + encryption args[:encryption] # may be nil + + if pr = @auth[:password] and pr.respond_to?(:call) + @auth[:password] = pr.call + end + + # This variable is only set when we are created with LDAP::open. + # All of our internal methods will connect using it, or else + # they will create their own. + @open_connection = nil + end + + # Convenience method to specify authentication credentials to the LDAP + # server. Currently supports simple authentication requiring + # a username and password. + # + # Observe that on most LDAP servers, + # the username is a complete DN. However, with A/D, it's often possible + # to give only a user-name rather than a complete DN. In the latter + # case, beware that many A/D servers are configured to permit anonymous + # (uncredentialled) binding, and will silently accept your binding + # as anonymous if you give an unrecognized username. This is not usually + # what you want. (See #get_operation_result.) + # + # <b>Important:</b> The password argument may be a Proc that returns a string. + # This makes it possible for you to write client programs that solicit + # passwords from users or from other data sources without showing them + # in your code or on command lines. + # + # require 'net/ldap' + # + # ldap = Net::LDAP.new + # ldap.host = server_ip_address + # ldap.authenticate "cn=Your Username,cn=Users,dc=example,dc=com", "your_psw" + # + # Alternatively (with a password block): + # + # require 'net/ldap' + # + # ldap = Net::LDAP.new + # ldap.host = server_ip_address + # psw = proc { your_psw_function } + # ldap.authenticate "cn=Your Username,cn=Users,dc=example,dc=com", psw + # + def authenticate username, password + password = password.call if password.respond_to?(:call) + @auth = {:method => :simple, :username => username, :password => password} + end + + alias_method :auth, :authenticate + + # Convenience method to specify encryption characteristics for connections + # to LDAP servers. Called implicitly by #new and #open, but may also be called + # by user code if desired. + # The single argument is generally a Hash (but see below for convenience alternatives). + # This implementation is currently a stub, supporting only a few encryption + # alternatives. As additional capabilities are added, more configuration values + # will be added here. + # + # Currently, the only supported argument is {:method => :simple_tls}. + # (Equivalently, you may pass the symbol :simple_tls all by itself, without + # enclosing it in a Hash.) + # + # The :simple_tls encryption method encrypts <i>all</i> communications with the LDAP + # server. + # It completely establishes SSL/TLS encryption with the LDAP server + # before any LDAP-protocol data is exchanged. + # There is no plaintext negotiation and no special encryption-request controls + # are sent to the server. + # <i>The :simple_tls option is the simplest, easiest way to encrypt communications + # between Net::LDAP and LDAP servers.</i> + # It's intended for cases where you have an implicit level of trust in the authenticity + # of the LDAP server. No validation of the LDAP server's SSL certificate is + # performed. This means that :simple_tls will not produce errors if the LDAP + # server's encryption certificate is not signed by a well-known Certification + # Authority. + # If you get communications or protocol errors when using this option, check + # with your LDAP server administrator. Pay particular attention to the TCP port + # you are connecting to. It's impossible for an LDAP server to support plaintext + # LDAP communications and <i>simple TLS</i> connections on the same port. + # The standard TCP port for unencrypted LDAP connections is 389, but the standard + # port for simple-TLS encrypted connections is 636. Be sure you are using the + # correct port. + # + # <i>[Note: a future version of Net::LDAP will support the STARTTLS LDAP control, + # which will enable encrypted communications on the same TCP port used for + # unencrypted connections.]</i> + # + def encryption args + if args == :simple_tls + args = {:method => :simple_tls} + end + @encryption = args + end + + + # #open takes the same parameters as #new. #open makes a network connection to the + # LDAP server and then passes a newly-created Net::LDAP object to the caller-supplied block. + # Within the block, you can call any of the instance methods of Net::LDAP to + # perform operations against the LDAP directory. #open will perform all the + # operations in the user-supplied block on the same network connection, which + # will be closed automatically when the block finishes. + # + # # (PSEUDOCODE) + # auth = {:method => :simple, :username => username, :password => password} + # Net::LDAP.open( :host => ipaddress, :port => 389, :auth => auth ) do |ldap| + # ldap.search( ... ) + # ldap.add( ... ) + # ldap.modify( ... ) + # end + # + def LDAP::open args + ldap1 = LDAP.new args + ldap1.open {|ldap| yield ldap } + end + + # Returns a meaningful result any time after + # a protocol operation (#bind, #search, #add, #modify, #rename, #delete) + # has completed. + # It returns an #OpenStruct containing an LDAP result code (0 means success), + # and a human-readable string. + # unless ldap.bind + # puts "Result: #{ldap.get_operation_result.code}" + # puts "Message: #{ldap.get_operation_result.message}" + # end + # + def get_operation_result + os = OpenStruct.new + if @result + os.code = @result + else + os.code = 0 + end + os.message = LDAP.result2string( os.code ) + os + end + + + # Opens a network connection to the server and then + # passes <tt>self</tt> to the caller-supplied block. The connection is + # closed when the block completes. Used for executing multiple + # LDAP operations without requiring a separate network connection + # (and authentication) for each one. + # <i>Note:</i> You do not need to log-in or "bind" to the server. This will + # be done for you automatically. + # For an even simpler approach, see the class method Net::LDAP#open. + # + # # (PSEUDOCODE) + # auth = {:method => :simple, :username => username, :password => password} + # ldap = Net::LDAP.new( :host => ipaddress, :port => 389, :auth => auth ) + # ldap.open do |ldap| + # ldap.search( ... ) + # ldap.add( ... ) + # ldap.modify( ... ) + # end + #-- + # First we make a connection and then a binding, but we don't + # do anything with the bind results. + # We then pass self to the caller's block, where he will execute + # his LDAP operations. Of course they will all generate auth failures + # if the bind was unsuccessful. + def open + raise LdapError.new( "open already in progress" ) if @open_connection + @open_connection = Connection.new( :host => @host, :port => @port, :encryption => @encryption ) + @open_connection.bind @auth + yield self + @open_connection.close + @open_connection = nil + end + + + # Searches the LDAP directory for directory entries. + # Takes a hash argument with parameters. Supported parameters include: + # * :base (a string specifying the tree-base for the search); + # * :filter (an object of type Net::LDAP::Filter, defaults to objectclass=*); + # * :attributes (a string or array of strings specifying the LDAP attributes to return from the server); + # * :return_result (a boolean specifying whether to return a result set). + # * :attributes_only (a boolean flag, defaults false) + # * :scope (one of: Net::LDAP::SearchScope_BaseObject, Net::LDAP::SearchScope_SingleLevel, Net::LDAP::SearchScope_WholeSubtree. Default is WholeSubtree.) + # + # #search queries the LDAP server and passes <i>each entry</i> to the + # caller-supplied block, as an object of type Net::LDAP::Entry. + # If the search returns 1000 entries, the block will + # be called 1000 times. If the search returns no entries, the block will + # not be called. + # + #-- + # ORIGINAL TEXT, replaced 04May06. + # #search returns either a result-set or a boolean, depending on the + # value of the <tt>:return_result</tt> argument. The default behavior is to return + # a result set, which is a hash. Each key in the hash is a string specifying + # the DN of an entry. The corresponding value for each key is a Net::LDAP::Entry object. + # If you request a result set and #search fails with an error, it will return nil. + # Call #get_operation_result to get the error information returned by + # the LDAP server. + #++ + # #search returns either a result-set or a boolean, depending on the + # value of the <tt>:return_result</tt> argument. The default behavior is to return + # a result set, which is an Array of objects of class Net::LDAP::Entry. + # If you request a result set and #search fails with an error, it will return nil. + # Call #get_operation_result to get the error information returned by + # the LDAP server. + # + # When <tt>:return_result => false,</tt> #search will + # return only a Boolean, to indicate whether the operation succeeded. This can improve performance + # with very large result sets, because the library can discard each entry from memory after + # your block processes it. + # + # + # treebase = "dc=example,dc=com" + # filter = Net::LDAP::Filter.eq( "mail", "a*.com" ) + # attrs = ["mail", "cn", "sn", "objectclass"] + # ldap.search( :base => treebase, :filter => filter, :attributes => attrs, :return_result => false ) do |entry| + # puts "DN: #{entry.dn}" + # entry.each do |attr, values| + # puts ".......#{attr}:" + # values.each do |value| + # puts " #{value}" + # end + # end + # end + # + #-- + # This is a re-implementation of search that replaces the + # original one (now renamed searchx and possibly destined to go away). + # The difference is that we return a dataset (or nil) from the + # call, and pass _each entry_ as it is received from the server + # to the caller-supplied block. This will probably make things + # far faster as we can do useful work during the network latency + # of the search. The downside is that we have no access to the + # whole set while processing the blocks, so we can't do stuff + # like sort the DNs until after the call completes. + # It's also possible that this interacts badly with server timeouts. + # We'll have to ensure that something reasonable happens if + # the caller has processed half a result set when we throw a timeout + # error. + # Another important difference is that we return a result set from + # this method rather than a T/F indication. + # Since this can be very heavy-weight, we define an argument flag + # that the caller can set to suppress the return of a result set, + # if he's planning to process every entry as it comes from the server. + # + # REINTERPRETED the result set, 04May06. Originally this was a hash + # of entries keyed by DNs. But let's get away from making users + # handle DNs. Change it to a plain array. Eventually we may + # want to return a Dataset object that delegates to an internal + # array, so we can provide sort methods and what-not. + # + def search args = {} + args[:base] ||= @base + result_set = (args and args[:return_result] == false) ? nil : [] + + if @open_connection + @result = @open_connection.search( args ) {|entry| + result_set << entry if result_set + yield( entry ) if block_given? + } + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port, :encryption => @encryption ) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.search( args ) {|entry| + result_set << entry if result_set + yield( entry ) if block_given? + } + end + conn.close + end + + @result == 0 and result_set + end + + # #bind connects to an LDAP server and requests authentication + # based on the <tt>:auth</tt> parameter passed to #open or #new. + # It takes no parameters. + # + # User code does not need to call #bind directly. It will be called + # implicitly by the library whenever you invoke an LDAP operation, + # such as #search or #add. + # + # It is useful, however, to call #bind in your own code when the + # only operation you intend to perform against the directory is + # to validate a login credential. #bind returns true or false + # to indicate whether the binding was successful. Reasons for + # failure include malformed or unrecognized usernames and + # incorrect passwords. Use #get_operation_result to find out + # what happened in case of failure. + # + # Here's a typical example using #bind to authenticate a + # credential which was (perhaps) solicited from the user of a + # web site: + # + # require 'net/ldap' + # ldap = Net::LDAP.new + # ldap.host = your_server_ip_address + # ldap.port = 389 + # ldap.auth your_user_name, your_user_password + # if ldap.bind + # # authentication succeeded + # else + # # authentication failed + # p ldap.get_operation_result + # end + # + # You don't have to create a new instance of Net::LDAP every time + # you perform a binding in this way. If you prefer, you can cache the Net::LDAP object + # and re-use it to perform subsequent bindings, <i>provided</i> you call + # #auth to specify a new credential before calling #bind. Otherwise, you'll + # just re-authenticate the previous user! (You don't need to re-set + # the values of #host and #port.) As noted in the documentation for #auth, + # the password parameter can be a Ruby Proc instead of a String. + # + #-- + # If there is an @open_connection, then perform the bind + # on it. Otherwise, connect, bind, and disconnect. + # The latter operation is obviously useful only as an auth check. + # + def bind auth=@auth + if @open_connection + @result = @open_connection.bind auth + else + conn = Connection.new( :host => @host, :port => @port , :encryption => @encryption) + @result = conn.bind @auth + conn.close + end + + @result == 0 + end + + # + # #bind_as is for testing authentication credentials. + # + # As described under #bind, most LDAP servers require that you supply a complete DN + # as a binding-credential, along with an authenticator such as a password. + # But for many applications (such as authenticating users to a Rails application), + # you often don't have a full DN to identify the user. You usually get a simple + # identifier like a username or an email address, along with a password. + # #bind_as allows you to authenticate these user-identifiers. + # + # #bind_as is a combination of a search and an LDAP binding. First, it connects and + # binds to the directory as normal. Then it searches the directory for an entry + # corresponding to the email address, username, or other string that you supply. + # If the entry exists, then #bind_as will <b>re-bind</b> as that user with the + # password (or other authenticator) that you supply. + # + # #bind_as takes the same parameters as #search, <i>with the addition of an + # authenticator.</i> Currently, this authenticator must be <tt>:password</tt>. + # Its value may be either a String, or a +proc+ that returns a String. + # #bind_as returns +false+ on failure. On success, it returns a result set, + # just as #search does. This result set is an Array of objects of + # type Net::LDAP::Entry. It contains the directory attributes corresponding to + # the user. (Just test whether the return value is logically true, if you don't + # need this additional information.) + # + # Here's how you would use #bind_as to authenticate an email address and password: + # + # require 'net/ldap' + # + # user,psw = "joe_user@yourcompany.com", "joes_psw" + # + # ldap = Net::LDAP.new + # ldap.host = "192.168.0.100" + # ldap.port = 389 + # ldap.auth "cn=manager,dc=yourcompany,dc=com", "topsecret" + # + # result = ldap.bind_as( + # :base => "dc=yourcompany,dc=com", + # :filter => "(mail=#{user})", + # :password => psw + # ) + # if result + # puts "Authenticated #{result.first.dn}" + # else + # puts "Authentication FAILED." + # end + def bind_as args={} + result = false + open {|me| + rs = search args + if rs and rs.first and dn = rs.first.dn + password = args[:password] + password = password.call if password.respond_to?(:call) + result = rs if bind :method => :simple, :username => dn, :password => password + end + } + result + end + + + # Adds a new entry to the remote LDAP server. + # Supported arguments: + # :dn :: Full DN of the new entry + # :attributes :: Attributes of the new entry. + # + # The attributes argument is supplied as a Hash keyed by Strings or Symbols + # giving the attribute name, and mapping to Strings or Arrays of Strings + # giving the actual attribute values. Observe that most LDAP directories + # enforce schema constraints on the attributes contained in entries. + # #add will fail with a server-generated error if your attributes violate + # the server-specific constraints. + # Here's an example: + # + # dn = "cn=George Smith,ou=people,dc=example,dc=com" + # attr = { + # :cn => "George Smith", + # :objectclass => ["top", "inetorgperson"], + # :sn => "Smith", + # :mail => "gsmith@example.com" + # } + # Net::LDAP.open (:host => host) do |ldap| + # ldap.add( :dn => dn, :attributes => attr ) + # end + # + def add args + if @open_connection + @result = @open_connection.add( args ) + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port, :encryption => @encryption) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.add( args ) + end + conn.close + end + @result == 0 + end + + + # Modifies the attribute values of a particular entry on the LDAP directory. + # Takes a hash with arguments. Supported arguments are: + # :dn :: (the full DN of the entry whose attributes are to be modified) + # :operations :: (the modifications to be performed, detailed next) + # + # This method returns True or False to indicate whether the operation + # succeeded or failed, with extended information available by calling + # #get_operation_result. + # + # Also see #add_attribute, #replace_attribute, or #delete_attribute, which + # provide simpler interfaces to this functionality. + # + # The LDAP protocol provides a full and well thought-out set of operations + # for changing the values of attributes, but they are necessarily somewhat complex + # and not always intuitive. If these instructions are confusing or incomplete, + # please send us email or create a bug report on rubyforge. + # + # The :operations parameter to #modify takes an array of operation-descriptors. + # Each individual operation is specified in one element of the array, and + # most LDAP servers will attempt to perform the operations in order. + # + # Each of the operations appearing in the Array must itself be an Array + # with exactly three elements: + # an operator:: must be :add, :replace, or :delete + # an attribute name:: the attribute name (string or symbol) to modify + # a value:: either a string or an array of strings. + # + # The :add operator will, unsurprisingly, add the specified values to + # the specified attribute. If the attribute does not already exist, + # :add will create it. Most LDAP servers will generate an error if you + # try to add a value that already exists. + # + # :replace will erase the current value(s) for the specified attribute, + # if there are any, and replace them with the specified value(s). + # + # :delete will remove the specified value(s) from the specified attribute. + # If you pass nil, an empty string, or an empty array as the value parameter + # to a :delete operation, the _entire_ _attribute_ will be deleted, along + # with all of its values. + # + # For example: + # + # dn = "mail=modifyme@example.com,ou=people,dc=example,dc=com" + # ops = [ + # [:add, :mail, "aliasaddress@example.com"], + # [:replace, :mail, ["newaddress@example.com", "newalias@example.com"]], + # [:delete, :sn, nil] + # ] + # ldap.modify :dn => dn, :operations => ops + # + # <i>(This example is contrived since you probably wouldn't add a mail + # value right before replacing the whole attribute, but it shows that order + # of execution matters. Also, many LDAP servers won't let you delete SN + # because that would be a schema violation.)</i> + # + # It's essential to keep in mind that if you specify more than one operation in + # a call to #modify, most LDAP servers will attempt to perform all of the operations + # in the order you gave them. + # This matters because you may specify operations on the + # same attribute which must be performed in a certain order. + # + # Most LDAP servers will _stop_ processing your modifications if one of them + # causes an error on the server (such as a schema-constraint violation). + # If this happens, you will probably get a result code from the server that + # reflects only the operation that failed, and you may or may not get extended + # information that will tell you which one failed. #modify has no notion + # of an atomic transaction. If you specify a chain of modifications in one + # call to #modify, and one of them fails, the preceding ones will usually + # not be "rolled back," resulting in a partial update. This is a limitation + # of the LDAP protocol, not of Net::LDAP. + # + # The lack of transactional atomicity in LDAP means that you're usually + # better off using the convenience methods #add_attribute, #replace_attribute, + # and #delete_attribute, which are are wrappers over #modify. However, certain + # LDAP servers may provide concurrency semantics, in which the several operations + # contained in a single #modify call are not interleaved with other + # modification-requests received simultaneously by the server. + # It bears repeating that this concurrency does _not_ imply transactional + # atomicity, which LDAP does not provide. + # + def modify args + if @open_connection + @result = @open_connection.modify( args ) + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port, :encryption => @encryption ) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.modify( args ) + end + conn.close + end + @result == 0 + end + + + # Add a value to an attribute. + # Takes the full DN of the entry to modify, + # the name (Symbol or String) of the attribute, and the value (String or + # Array). If the attribute does not exist (and there are no schema violations), + # #add_attribute will create it with the caller-specified values. + # If the attribute already exists (and there are no schema violations), the + # caller-specified values will be _added_ to the values already present. + # + # Returns True or False to indicate whether the operation + # succeeded or failed, with extended information available by calling + # #get_operation_result. See also #replace_attribute and #delete_attribute. + # + # dn = "cn=modifyme,dc=example,dc=com" + # ldap.add_attribute dn, :mail, "newmailaddress@example.com" + # + def add_attribute dn, attribute, value + modify :dn => dn, :operations => [[:add, attribute, value]] + end + + # Replace the value of an attribute. + # #replace_attribute can be thought of as equivalent to calling #delete_attribute + # followed by #add_attribute. It takes the full DN of the entry to modify, + # the name (Symbol or String) of the attribute, and the value (String or + # Array). If the attribute does not exist, it will be created with the + # caller-specified value(s). If the attribute does exist, its values will be + # _discarded_ and replaced with the caller-specified values. + # + # Returns True or False to indicate whether the operation + # succeeded or failed, with extended information available by calling + # #get_operation_result. See also #add_attribute and #delete_attribute. + # + # dn = "cn=modifyme,dc=example,dc=com" + # ldap.replace_attribute dn, :mail, "newmailaddress@example.com" + # + def replace_attribute dn, attribute, value + modify :dn => dn, :operations => [[:replace, attribute, value]] + end + + # Delete an attribute and all its values. + # Takes the full DN of the entry to modify, and the + # name (Symbol or String) of the attribute to delete. + # + # Returns True or False to indicate whether the operation + # succeeded or failed, with extended information available by calling + # #get_operation_result. See also #add_attribute and #replace_attribute. + # + # dn = "cn=modifyme,dc=example,dc=com" + # ldap.delete_attribute dn, :mail + # + def delete_attribute dn, attribute + modify :dn => dn, :operations => [[:delete, attribute, nil]] + end + + + # Rename an entry on the remote DIS by changing the last RDN of its DN. + # _Documentation_ _stub_ + # + def rename args + if @open_connection + @result = @open_connection.rename( args ) + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port, :encryption => @encryption ) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.rename( args ) + end + conn.close + end + @result == 0 + end + + # modify_rdn is an alias for #rename. + def modify_rdn args + rename args + end + + # Delete an entry from the LDAP directory. + # Takes a hash of arguments. + # The only supported argument is :dn, which must + # give the complete DN of the entry to be deleted. + # Returns True or False to indicate whether the delete + # succeeded. Extended status information is available by + # calling #get_operation_result. + # + # dn = "mail=deleteme@example.com,ou=people,dc=example,dc=com" + # ldap.delete :dn => dn + # + def delete args + if @open_connection + @result = @open_connection.delete( args ) + else + @result = 0 + conn = Connection.new( :host => @host, :port => @port, :encryption => @encryption ) + if (@result = conn.bind( args[:auth] || @auth )) == 0 + @result = conn.delete( args ) + end + conn.close + end + @result == 0 + end + + end # class LDAP + + + + class LDAP + # This is a private class used internally by the library. It should not be called by user code. + class Connection # :nodoc: + + LdapVersion = 3 + + + #-- + # initialize + # + def initialize server + begin + @conn = TCPsocket.new( server[:host], server[:port] ) + rescue + raise LdapError.new( "no connection to server" ) + end + + if server[:encryption] + setup_encryption server[:encryption] + end + + yield self if block_given? + end + + + #-- + # Helper method called only from new, and only after we have a successfully-opened + # @conn instance variable, which is a TCP connection. + # Depending on the received arguments, we establish SSL, potentially replacing + # the value of @conn accordingly. + # Don't generate any errors here if no encryption is requested. + # DO raise LdapError objects if encryption is requested and we have trouble setting + # it up. That includes if OpenSSL is not set up on the machine. (Question: + # how does the Ruby OpenSSL wrapper react in that case?) + # DO NOT filter exceptions raised by the OpenSSL library. Let them pass back + # to the user. That should make it easier for us to debug the problem reports. + # Presumably (hopefully?) that will also produce recognizable errors if someone + # tries to use this on a machine without OpenSSL. + # + # The simple_tls method is intended as the simplest, stupidest, easiest solution + # for people who want nothing more than encrypted comms with the LDAP server. + # It doesn't do any server-cert validation and requires nothing in the way + # of key files and root-cert files, etc etc. + # OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected + # TCPsocket object. + # + def setup_encryption args + case args[:method] + when :simple_tls + raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available + ctx = OpenSSL::SSL::SSLContext.new + @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx) + @conn.connect + @conn.sync_close = true + # additional branches requiring server validation and peer certs, etc. go here. + else + raise LdapError.new( "unsupported encryption method #{args[:method]}" ) + end + end + + #-- + # close + # This is provided as a convenience method to make + # sure a connection object gets closed without waiting + # for a GC to happen. Clients shouldn't have to call it, + # but perhaps it will come in handy someday. + def close + @conn.close + @conn = nil + end + + #-- + # next_msgid + # + def next_msgid + @msgid ||= 0 + @msgid += 1 + end + + + #-- + # bind + # + def bind auth + user,psw = case auth[:method] + when :anonymous + ["",""] + when :simple + [auth[:username] || auth[:dn], auth[:password]] + end + raise LdapError.new( "invalid binding information" ) unless (user && psw) + + msgid = next_msgid.to_ber + request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0) + request_pkt = [msgid, request].to_ber_sequence + @conn.write request_pkt + + (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" ) + pdu.result_code + end + + #-- + # search + # Alternate implementation, this yields each search entry to the caller + # as it are received. + # TODO, certain search parameters are hardcoded. + # TODO, if we mis-parse the server results or the results are wrong, we can block + # forever. That's because we keep reading results until we get a type-5 packet, + # which might never come. We need to support the time-limit in the protocol. + #-- + # WARNING: this code substantially recapitulates the searchx method. + # + # 02May06: Well, I added support for RFC-2696-style paged searches. + # This is used on all queries because the extension is marked non-critical. + # As far as I know, only A/D uses this, but it's required for A/D. Otherwise + # you won't get more than 1000 results back from a query. + # This implementation is kindof clunky and should probably be refactored. + # Also, is it my imagination, or are A/Ds the slowest directory servers ever??? + # + def search args = {} + search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) + search_filter = Filter.construct(search_filter) if search_filter.is_a?(String) + search_base = (args && args[:base]) || "dc=example,dc=com" + search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} + return_referrals = args && args[:return_referrals] == true + + attributes_only = (args and args[:attributes_only] == true) + scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree + raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope) + + # An interesting value for the size limit would be close to A/D's built-in + # page limit of 1000 records, but openLDAP newer than version 2.2.0 chokes + # on anything bigger than 126. You get a silent error that is easily visible + # by running slapd in debug mode. Go figure. + rfc2696_cookie = [126, ""] + result_code = 0 + + loop { + # should collect this into a private helper to clarify the structure + + request = [ + search_base.to_ber, + scope.to_ber_enumerated, + 0.to_ber_enumerated, + 0.to_ber, + 0.to_ber, + attributes_only.to_ber, + search_filter.to_ber, + search_attributes.to_ber_sequence + ].to_ber_appsequence(3) + + controls = [ + [ + LdapControls::PagedResults.to_ber, + false.to_ber, # criticality MUST be false to interoperate with normal LDAPs. + rfc2696_cookie.map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber + ].to_ber_sequence + ].to_ber_contextspecific(0) + + pkt = [next_msgid.to_ber, request, controls].to_ber_sequence + @conn.write pkt + + result_code = 0 + controls = [] + + while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) + case pdu.app_tag + when 4 # search-data + yield( pdu.search_entry ) if block_given? + when 19 # search-referral + if return_referrals + if block_given? + se = Net::LDAP::Entry.new + se[:search_referrals] = (pdu.search_referrals || []) + yield se + end + end + #p pdu.referrals + when 5 # search-result + result_code = pdu.result_code + controls = pdu.result_controls + break + else + raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" ) + end + end + + # When we get here, we have seen a type-5 response. + # If there is no error AND there is an RFC-2696 cookie, + # then query again for the next page of results. + # If not, we're done. + # Don't screw this up or we'll break every search we do. + more_pages = false + if result_code == 0 and controls + controls.each do |c| + if c.oid == LdapControls::PagedResults + more_pages = false # just in case some bogus server sends us >1 of these. + if c.value and c.value.length > 0 + cookie = c.value.read_ber[1] + if cookie and cookie.length > 0 + rfc2696_cookie[1] = cookie + more_pages = true + end + end + end + end + end + + break unless more_pages + } # loop + + result_code + end + + + + + #-- + # modify + # TODO, need to support a time limit, in case the server fails to respond. + # TODO!!! We're throwing an exception here on empty DN. + # Should return a proper error instead, probaby from farther up the chain. + # TODO!!! If the user specifies a bogus opcode, we'll throw a + # confusing error here ("to_ber_enumerated is not defined on nil"). + # + def modify args + modify_dn = args[:dn] or raise "Unable to modify empty DN" + modify_ops = [] + a = args[:operations] and a.each {|op, attr, values| + # TODO, fix the following line, which gives a bogus error + # if the opcode is invalid. + op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated + modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence + } + + request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6) + pkt = [next_msgid.to_ber, request].to_ber_sequence + @conn.write pkt + + (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" ) + pdu.result_code + end + + + #-- + # add + # TODO, need to support a time limit, in case the server fails to respond. + # + def add args + add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN") + add_attrs = [] + a = args[:attributes] and a.each {|k,v| + add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence + } + + request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8) + pkt = [next_msgid.to_ber, request].to_ber_sequence + @conn.write pkt + + (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" ) + pdu.result_code + end + + + #-- + # rename + # TODO, need to support a time limit, in case the server fails to respond. + # + def rename args + old_dn = args[:olddn] or raise "Unable to rename empty DN" + new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN" + delete_attrs = args[:delete_attributes] ? true : false + + request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12) + pkt = [next_msgid.to_ber, request].to_ber_sequence + @conn.write pkt + + (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" ) + pdu.result_code + end + + + #-- + # delete + # TODO, need to support a time limit, in case the server fails to respond. + # + def delete args + dn = args[:dn] or raise "Unable to delete empty DN" + + request = dn.to_s.to_ber_application_string(10) + pkt = [next_msgid.to_ber, request].to_ber_sequence + @conn.write pkt + + (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" ) + pdu.result_code + end + + + end # class Connection + end # class LDAP + + +end # module Net + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/dataset.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/dataset.rb new file mode 100644 index 000000000..1480a8f84 --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/dataset.rb @@ -0,0 +1,108 @@ +# $Id: dataset.rb 78 2006-04-26 02:57:34Z blackhedd $ +# +# +#---------------------------------------------------------------------------- +# +# 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 LDAP + +class Dataset < Hash + + attr_reader :comments + + + def Dataset::read_ldif io + ds = Dataset.new + + line = io.gets && chomp + dn = nil + + while line + io.gets and chomp + if $_ =~ /^[\s]+/ + line << " " << $' + else + nextline = $_ + + if line =~ /^\#/ + ds.comments << line + elsif line =~ /^dn:[\s]*/i + dn = $' + ds[dn] = Hash.new {|k,v| k[v] = []} + elsif line.length == 0 + dn = nil + elsif line =~ /^([^:]+):([\:]?)[\s]*/ + # $1 is the attribute name + # $2 is a colon iff the attr-value is base-64 encoded + # $' is the attr-value + # Avoid the Base64 class because not all Ruby versions have it. + attrvalue = ($2 == ":") ? $'.unpack('m').shift : $' + ds[dn][$1.downcase.intern] << attrvalue + end + + line = nextline + end + end + + ds + end + + + def initialize + @comments = [] + end + + + def to_ldif + ary = [] + ary += (@comments || []) + + keys.sort.each {|dn| + ary << "dn: #{dn}" + + self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr| + self[dn][attr.intern].each {|val| + ary << "#{attr}: #{val}" + } + } + + ary << "" + } + + block_given? and ary.each {|line| yield line} + + ary + end + + +end # Dataset + +end # LDAP +end # Net + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb new file mode 100644 index 000000000..8978545ee --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/entry.rb @@ -0,0 +1,165 @@ +# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $ +# +# LDAP Entry (search-result) 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 LDAP + + + # Objects of this class represent individual entries in an LDAP + # directory. User code generally does not instantiate this class. + # Net::LDAP#search provides objects of this class to user code, + # either as block parameters or as return values. + # + # In LDAP-land, an "entry" is a collection of attributes that are + # uniquely and globally identified by a DN ("Distinguished Name"). + # Attributes are identified by short, descriptive words or phrases. + # Although a directory is + # free to implement any attribute name, most of them follow rigorous + # standards so that the range of commonly-encountered attribute + # names is not large. + # + # An attribute name is case-insensitive. Most directories also + # restrict the range of characters allowed in attribute names. + # To simplify handling attribute names, Net::LDAP::Entry + # internally converts them to a standard format. Therefore, the + # methods which take attribute names can take Strings or Symbols, + # and work correctly regardless of case or capitalization. + # + # An attribute consists of zero or more data items called + # <i>values.</i> An entry is the combination of a unique DN, a set of attribute + # names, and a (possibly-empty) array of values for each attribute. + # + # Class Net::LDAP::Entry provides convenience methods for dealing + # with LDAP entries. + # In addition to the methods documented below, you may access individual + # attributes of an entry simply by giving the attribute name as + # the name of a method call. For example: + # ldap.search( ... ) do |entry| + # puts "Common name: #{entry.cn}" + # puts "Email addresses:" + # entry.mail.each {|ma| puts ma} + # end + # If you use this technique to access an attribute that is not present + # in a particular Entry object, a NoMethodError exception will be raised. + # + #-- + # Ugly problem to fix someday: We key off the internal hash with + # a canonical form of the attribute name: convert to a string, + # downcase, then take the symbol. Unfortunately we do this in + # at least three places. Should do it in ONE place. + class Entry + + # This constructor is not generally called by user code. + def initialize dn = nil # :nodoc: + @myhash = Hash.new {|k,v| k[v] = [] } + @myhash[:dn] = [dn] + end + + + def []= name, value # :nodoc: + sym = name.to_s.downcase.intern + @myhash[sym] = value + end + + + #-- + # We have to deal with this one as we do with []= + # because this one and not the other one gets called + # in formulations like entry["CN"] << cn. + # + def [] name # :nodoc: + name = name.to_s.downcase.intern unless name.is_a?(Symbol) + @myhash[name] + end + + # Returns the dn of the Entry as a String. + def dn + self[:dn][0] + end + + # Returns an array of the attribute names present in the Entry. + def attribute_names + @myhash.keys + end + + # Accesses each of the attributes present in the Entry. + # Calls a user-supplied block with each attribute in turn, + # passing two arguments to the block: a Symbol giving + # the name of the attribute, and a (possibly empty) + # Array of data values. + # + def each + if block_given? + attribute_names.each {|a| + attr_name,values = a,self[a] + yield attr_name, values + } + end + end + + alias_method :each_attribute, :each + + + #-- + # Convenience method to convert unknown method names + # to attribute references. Of course the method name + # comes to us as a symbol, so let's save a little time + # and not bother with the to_s.downcase two-step. + # Of course that means that a method name like mAIL + # won't work, but we shouldn't be encouraging that + # kind of bad behavior in the first place. + # Maybe we should thow something if the caller sends + # arguments or a block... + # + def method_missing *args, &block # :nodoc: + s = args[0].to_s.downcase.intern + if attribute_names.include?(s) + self[s] + elsif s.to_s[-1] == 61 and s.to_s.length > 1 + value = args[1] or raise RuntimeError.new( "unable to set value" ) + value = [value] unless value.is_a?(Array) + name = s.to_s[0..-2].intern + self[name] = value + else + raise NoMethodError.new( "undefined method '#{s}'" ) + end + end + + def write + end + + end # class Entry + + +end # class LDAP +end # module Net + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/filter.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/filter.rb new file mode 100644 index 000000000..38944a710 --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/filter.rb @@ -0,0 +1,387 @@ +# $Id: filter.rb 151 2006-08-15 08:34:53Z blackhedd $ +# +# +#---------------------------------------------------------------------------- +# +# 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 LDAP + + +# Class Net::LDAP::Filter is used to constrain +# LDAP searches. An object of this class is +# passed to Net::LDAP#search in the parameter :filter. +# +# Net::LDAP::Filter supports the complete set of search filters +# available in LDAP, including conjunction, disjunction and negation +# (AND, OR, and NOT). This class supplants the (infamous) RFC-2254 +# standard notation for specifying LDAP search filters. +# +# Here's how to code the familiar "objectclass is present" filter: +# f = Net::LDAP::Filter.pres( "objectclass" ) +# The object returned by this code can be passed directly to +# the <tt>:filter</tt> parameter of Net::LDAP#search. +# +# See the individual class and instance methods below for more examples. +# +class Filter + + def initialize op, a, b + @op = op + @left = a + @right = b + end + + # #eq creates a filter object indicating that the value of + # a paticular attribute must be either <i>present</i> or must + # match a particular string. + # + # To specify that an attribute is "present" means that only + # directory entries which contain a value for the particular + # attribute will be selected by the filter. This is useful + # in case of optional attributes such as <tt>mail.</tt> + # Presence is indicated by giving the value "*" in the second + # parameter to #eq. This example selects only entries that have + # one or more values for <tt>sAMAccountName:</tt> + # f = Net::LDAP::Filter.eq( "sAMAccountName", "*" ) + # + # To match a particular range of values, pass a string as the + # second parameter to #eq. The string may contain one or more + # "*" characters as wildcards: these match zero or more occurrences + # of any character. Full regular-expressions are <i>not</i> supported + # due to limitations in the underlying LDAP protocol. + # This example selects any entry with a <tt>mail</tt> value containing + # the substring "anderson": + # f = Net::LDAP::Filter.eq( "mail", "*anderson*" ) + #-- + # Removed gt and lt. They ain't in the standard! + # + def Filter::eq attribute, value; Filter.new :eq, attribute, value; end + def Filter::ne attribute, value; Filter.new :ne, attribute, value; end + #def Filter::gt attribute, value; Filter.new :gt, attribute, value; end + #def Filter::lt attribute, value; Filter.new :lt, attribute, value; end + def Filter::ge attribute, value; Filter.new :ge, attribute, value; end + def Filter::le attribute, value; Filter.new :le, attribute, value; end + + # #pres( attribute ) is a synonym for #eq( attribute, "*" ) + # + def Filter::pres attribute; Filter.eq attribute, "*"; end + + # operator & ("AND") is used to conjoin two or more filters. + # This expression will select only entries that have an <tt>objectclass</tt> + # attribute AND have a <tt>mail</tt> attribute that begins with "George": + # f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" ) + # + def & filter; Filter.new :and, self, filter; end + + # operator | ("OR") is used to disjoin two or more filters. + # This expression will select entries that have either an <tt>objectclass</tt> + # attribute OR a <tt>mail</tt> attribute that begins with "George": + # f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" ) + # + def | filter; Filter.new :or, self, filter; end + + + # + # operator ~ ("NOT") is used to negate a filter. + # This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt> + # attribute: + # f = ~ Net::LDAP::Filter.pres( "objectclass" ) + # + #-- + # This operator can't be !, evidently. Try it. + # Removed GT and LT. They're not in the RFC. + def ~@; Filter.new :not, self, nil; end + + + def to_s + case @op + when :ne + "(!(#{@left}=#{@right}))" + when :eq + "(#{@left}=#{@right})" + #when :gt + # "#{@left}>#{@right}" + #when :lt + # "#{@left}<#{@right}" + when :ge + "#{@left}>=#{@right}" + when :le + "#{@left}<=#{@right}" + when :and + "(&(#{@left})(#{@right}))" + when :or + "(|(#{@left})(#{@right}))" + when :not + "(!(#{@left}))" + else + raise "invalid or unsupported operator in LDAP Filter" + end + end + + + #-- + # to_ber + # Filter ::= + # CHOICE { + # and [0] SET OF Filter, + # or [1] SET OF Filter, + # not [2] Filter, + # equalityMatch [3] AttributeValueAssertion, + # substrings [4] SubstringFilter, + # greaterOrEqual [5] AttributeValueAssertion, + # lessOrEqual [6] AttributeValueAssertion, + # present [7] AttributeType, + # approxMatch [8] AttributeValueAssertion + # } + # + # SubstringFilter + # SEQUENCE { + # type AttributeType, + # SEQUENCE OF CHOICE { + # initial [0] LDAPString, + # any [1] LDAPString, + # final [2] LDAPString + # } + # } + # + # Parsing substrings is a little tricky. + # We use the split method to break a string into substrings + # delimited by the * (star) character. But we also need + # to know whether there is a star at the head and tail + # of the string. A Ruby particularity comes into play here: + # if you split on * and the first character of the string is + # a star, then split will return an array whose first element + # is an _empty_ string. But if the _last_ character of the + # string is star, then split will return an array that does + # _not_ add an empty string at the end. So we have to deal + # with all that specifically. + # + def to_ber + case @op + when :eq + if @right == "*" # present + @left.to_s.to_ber_contextspecific 7 + elsif @right =~ /[\*]/ #substring + ary = @right.split( /[\*]+/ ) + final_star = @right =~ /[\*]$/ + initial_star = ary.first == "" and ary.shift + + seq = [] + unless initial_star + seq << ary.shift.to_ber_contextspecific(0) + end + n_any_strings = ary.length - (final_star ? 0 : 1) + #p n_any_strings + n_any_strings.times { + seq << ary.shift.to_ber_contextspecific(1) + } + unless final_star + seq << ary.shift.to_ber_contextspecific(2) + end + [@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4 + else #equality + [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3 + end + when :ge + [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5 + when :le + [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6 + when :and + ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten + ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 ) + when :or + ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten + ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 ) + when :not + [@left.to_ber].to_ber_contextspecific 2 + else + # ERROR, we'll return objectclass=* to keep things from blowing up, + # but that ain't a good answer and we need to kick out an error of some kind. + raise "unimplemented search filter" + end + end + + #-- + # coalesce + # This is a private helper method for dealing with chains of ANDs and ORs + # that are longer than two. If BOTH of our branches are of the specified + # type of joining operator, then return both of them as an array (calling + # coalesce recursively). If they're not, then return an array consisting + # only of self. + # + def coalesce operator + if @op == operator + [@left.coalesce( operator ), @right.coalesce( operator )] + else + [self] + end + end + + + + #-- + # We get a Ruby object which comes from parsing an RFC-1777 "Filter" + # object. Convert it to a Net::LDAP::Filter. + # TODO, we're hardcoding the RFC-1777 BER-encodings of the various + # filter types. Could pull them out into a constant. + # + def Filter::parse_ldap_filter obj + case obj.ber_identifier + when 0x87 # present. context-specific primitive 7. + Filter.eq( obj.to_s, "*" ) + when 0xa3 # equalityMatch. context-specific constructed 3. + Filter.eq( obj[0], obj[1] ) + else + raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" ) + end + end + + + #-- + # We got a hash of attribute values. + # Do we match the attributes? + # Return T/F, and call match recursively as necessary. + def match entry + case @op + when :eq + if @right == "*" + l = entry[@left] and l.length > 0 + else + l = entry[@left] and l = l.to_a and l.index(@right) + end + else + raise LdapError.new( "unknown filter type in match: #{@op}" ) + end + end + + # Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254) + # to a Net::LDAP::Filter. + def self.construct ldap_filter_string + FilterParser.new(ldap_filter_string).filter + end + + # Synonym for #construct. + # to a Net::LDAP::Filter. + def self.from_rfc2254 ldap_filter_string + construct ldap_filter_string + end + +end # class Net::LDAP::Filter + + + +class FilterParser #:nodoc: + + attr_reader :filter + + def initialize str + require 'strscan' + @filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" ) + end + + def parse scanner + parse_filter_branch(scanner) or parse_paren_expression(scanner) + end + + def parse_paren_expression scanner + if scanner.scan /\s*\(\s*/ + b = if scanner.scan /\s*\&\s*/ + a = nil + branches = [] + while br = parse_paren_expression(scanner) + branches << br + end + if branches.length >= 2 + a = branches.shift + while branches.length > 0 + a = a & branches.shift + end + a + end + elsif scanner.scan /\s*\|\s*/ + # TODO: DRY! + a = nil + branches = [] + while br = parse_paren_expression(scanner) + branches << br + end + if branches.length >= 2 + a = branches.shift + while branches.length > 0 + a = a | branches.shift + end + a + end + elsif scanner.scan /\s*\!\s*/ + br = parse_paren_expression(scanner) + if br + ~ br + end + else + parse_filter_branch( scanner ) + end + + if b and scanner.scan( /\s*\)\s*/ ) + b + end + end + end + + # Added a greatly-augmented filter contributed by Andre Nathan + # for detecting special characters in values. (15Aug06) + def parse_filter_branch scanner + scanner.scan /\s*/ + if token = scanner.scan( /[\w\-_]+/ ) + scanner.scan /\s*/ + if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ ) + scanner.scan /\s*/ + #if value = scanner.scan( /[\w\*\.]+/ ) (ORG) + if value = scanner.scan( /[\w\*\.\+\-@=#\$%&!]+/ ) + case op + when "=" + Filter.eq( token, value ) + when "!=" + Filter.ne( token, value ) + when "<" + Filter.lt( token, value ) + when "<=" + Filter.le( token, value ) + when ">" + Filter.gt( token, value ) + when ">=" + Filter.ge( token, value ) + end + end + end + end + end + +end # class Net::LDAP::FilterParser + +end # class Net::LDAP +end # module Net + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb new file mode 100644 index 000000000..dbc0d6f10 --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/pdu.rb @@ -0,0 +1,205 @@ +# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $ +# +# 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 + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb new file mode 100644 index 000000000..89d1ffdf2 --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldap/psw.rb @@ -0,0 +1,64 @@ +# $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $ +# +# +#---------------------------------------------------------------------------- +# +# 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 LDAP + + +class Password + class << self + + # Generate a password-hash suitable for inclusion in an LDAP attribute. + # Pass a hash type (currently supported: :md5 and :sha) and a plaintext + # password. This function will return a hashed representation. + # STUB: This is here to fulfill the requirements of an RFC, which one? + # TODO, gotta do salted-sha and (maybe) salted-md5. + # Should we provide sha1 as a synonym for sha1? I vote no because then + # should you also provide ssha1 for symmetry? + def generate( type, str ) + case type + when :md5 + require 'md5' + "{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }" + when :sha + require 'sha1' + "{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }" + # when ssha + else + raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" ) + end + end + + end +end + + +end # class LDAP +end # module Net + + diff --git a/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldif.rb b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldif.rb new file mode 100644 index 000000000..1641bda4b --- /dev/null +++ b/vendor/plugins/ruby-net-ldap-0.0.4/lib/net/ldif.rb @@ -0,0 +1,39 @@ +# $Id: ldif.rb 78 2006-04-26 02:57:34Z blackhedd $ +# +# Net::LDIF for Ruby +# +# +# +# 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 +# +# + +# THIS FILE IS A STUB. + +module Net + + class LDIF + + + end # class LDIF + + +end # module Net + + |