Cleaned up the layout quite a bit to make Gemifying and including into Rails 3 less painful. Some steps to 1.9 compatibility

This commit is contained in:
Rory OConnell 2010-02-09 16:46:49 -06:00
parent ba08042d75
commit d37c3b3ae6
22 changed files with 603 additions and 2313 deletions

View file

@ -1,37 +0,0 @@
COPYING
History.txt
LICENSE
Manifest.txt
README.txt
Rakefile
Release-Announcement
lib/net/ber.rb
lib/net/ldap.rb
lib/net/ldap/dataset.rb
lib/net/ldap/entry.rb
lib/net/ldap/filter.rb
lib/net/ldap/pdu.rb
lib/net/ldap/psw.rb
lib/net/ldif.rb
lib/net/snmp.rb
pre-setup.rb
setup.rb
test/common.rb
test/test_ber.rb
test/test_entry.rb
test/test_filter.rb
test/test_ldif.rb
test/test_password.rb
test/test_snmp.rb
test/testdata.ldif
tests/NOTICE.txt
tests/testber.rb
tests/testdata.ldif
tests/testem.rb
tests/testfilter.rb
tests/testldap.rb
tests/testldif.rb
tests/testpsw.rb
tests/testsnmp.rb
testserver/ldapserver.rb
testserver/testdata.ldif

View file

@ -1,95 +0,0 @@
We're pleased to announce version 0.0.4 of Net::LDAP, the pure-Ruby LDAP library.
This version adds an implementation of Net::LDAP#bind_as, which allows
you to authenticate users who log into your applications using simple
identifiers like email addresses and simple usernames. Thanks to Simon Claret
for suggesting the original implementation in his page on the Rails-Wiki,
and for valuable comments and help with testing.
We have un-deprecated Net::LDAP#modify, which can be useful with
LDAP servers that observe non-standard transactional and concurrency
semantics in LDAP Modify operations. Note: this is a documentation change,
not a code change. Thanks to Justin Forder for providing the rationale
for this change.
We added a larger set of special characters which may appear in RFC-2254
standard search filters. Thanks to Andre Nathan for this patch.
We fixed a bug that was preventing Net::LDAP#open from being called more
than once on the same object.
Net::LDAP is a feature-complete LDAP client which can access as much as
possible of the functionality of the most-used LDAP server implementations.
This library does not wrap any existing native-code LDAP libraries, creates no
Ruby extensions, and has no dependencies external to Ruby.
If anyone wants to contribute suggestions, insights or (especially)
code, please email me at garbagecat10 .. .. gmail.com.
= What is Net::LDAP for Ruby?
This library provides a pure-Ruby implementation of an LDAP client.
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 of the LDAP protocol itself, so as to make the
programming interface as Ruby-like as possible.
In particular, this means that there is no direct dependence on the
structure of the various "traditional" LDAP clients. This is a ground-up
rethinking of the LDAP API.
Net::LDAP is based on RFC-2251, which specifies the Lightweight Directory
Access Protocol, as amended and extended by subsequent RFCs and by the more
widely-used directory implementations.
Homepage:: http://rubyforge.org/projects/net-ldap/
Download:: http://rubyforge.org/frs/?group_id=143
Copyright:: 2006 by Francis Cianfrocca
== LICENCE NOTES
Please read the file LICENCE for licensing restrictions on this library. In
the simplest terms, this library is available under the same terms as Ruby
itself.
== Requirements and Installation
Net::LDAP requires Ruby 1.8.2 or better.
Net::LDAP can be installed with:
% ruby setup.rb
Alternatively, you can use the RubyGems version of Net::LDAP available
as ruby-net-ldap-0.0.2.gem from the usual sources.
== Whet your appetite:
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
== Net::LDAP 0.0.2: May 3, 2006
* Fixed malformation in distro tarball and gem.
* Improved documentation.
* Supported "paged search control."

38
lib/net.rb Normal file
View file

@ -0,0 +1,38 @@
require 'stringio'
require 'openssl'
require 'socket'
require 'ostruct'
require 'base64'
require 'strscan'
if RUBY_VERSION =~ /^1.9/
begin
SHA1
rescue NameError
require 'digest/sha1'
SHA1 = Digest::SHA1
end
begin
MD5
rescue NameError
require 'digest/md5'
MD5 = Digest::MD5
end
end
if RUBY_VERSION =~ /^1.8/
require 'md5'
require 'sha1'
end
module Net
autoload :BER, 'net/ber'
autoload :LDAP, 'net/ldap'
autoload :LDIF, 'net/ldif'
autoload :SNMP, 'net/snmp'
module BER
autoload :BERParser, 'net/ber/ber_parser'
end
end
require 'net/ldap/core_ext/all'

View file

@ -28,529 +28,80 @@
# #
# #
module Net
module BER
#--
# This condenses our nicely self-documenting ASN hashes down
# to an array for fast lookups.
# Scoped to be called as a module method, but not intended for
# user code to call.
#
def self.compile_syntax(syn)
out = [nil] * 256
syn.each do |tclass, tclasses|
tagclass = {:universal=>0, :application=>64, :context_specific=>128, :private=>192} [tclass]
tclasses.each do |codingtype,codings|
encoding = {:primitive=>0, :constructed=>32} [codingtype]
codings.each {|tag, objtype| out[tagclass + encoding + tag] = objtype }
end
end
out
end
def to_ber
# Provisional implementation.
# We ASSUME that our incoming value is an array, and we
# use the Array#to_ber_oid method defined below.
# We probably should obsolete that method, actually, in
# and move the code here.
# WE ARE NOT CURRENTLY ENCODING THE BER-IDENTIFIER.
# This implementation currently hardcodes 6, the universal OID tag.
ary = @value.dup
first = ary.shift
raise Net::BER::BerError.new(" invalid OID" ) unless [0,1,2].include?(first)
first = first * 40 + ary.shift
ary.unshift first
oid = ary.pack("w*")
[6, oid.length].pack("CC") + oid
end
end
end
module Net module Net
module BER module BER
class BerError < StandardError; end
class BerError < StandardError; end
class BerIdentifiedString < String
attr_accessor :ber_identifier
class BerIdentifiedString < String def initialize args
attr_accessor :ber_identifier super args
def initialize args
super args
end
end
class BerIdentifiedArray < Array
attr_accessor :ber_identifier
def initialize
super
end
end
class BerIdentifiedNull
attr_accessor :ber_identifier
def to_ber
"\005\000"
end
end
class BerIdentifiedOid
attr_accessor :ber_identifier
def initialize oid
if oid.is_a?(String)
oid = oid.split(/\./).map {|s| s.to_i }
end
@value = oid
end
def to_ber
# Provisional implementation.
# We ASSUME that our incoming value is an array, and we
# use the Array#to_ber_oid method defined below.
# We probably should obsolete that method, actually, in
# and move the code here.
# WE ARE NOT CURRENTLY ENCODING THE BER-IDENTIFIER.
# This implementation currently hardcodes 6, the universal OID tag.
ary = @value.dup
first = ary.shift
raise Net::BER::BerError.new(" invalid OID" ) unless [0,1,2].include?(first)
first = first * 40 + ary.shift
ary.unshift first
oid = ary.pack("w*")
[6, oid.length].pack("CC") + oid
end
end
#--
# This condenses our nicely self-documenting ASN hashes down
# to an array for fast lookups.
# Scoped to be called as a module method, but not intended for
# user code to call.
#
def self.compile_syntax syn
out = [nil] * 256
syn.each {|tclass,tclasses|
tagclass = {:universal=>0, :application=>64, :context_specific=>128, :private=>192} [tclass]
tclasses.each {|codingtype,codings|
encoding = {:primitive=>0, :constructed=>32} [codingtype]
codings.each {|tag,objtype|
out[tagclass + encoding + tag] = objtype
}
}
}
out
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 = BER.compile_syntax( {
:universal => {
:primitive => {
1 => :boolean,
2 => :integer,
4 => :string,
5 => :null,
6 => :oid,
10 => :integer,
13 => :string # (relative OID)
},
:constructed => {
16 => :array,
17 => :array
}
},
:context_specific => {
:primitive => {
10 => :integer
}
}
})
#
# 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.
#--
# BEWARE, this violates DRY and is largely equal in functionality to
# read_ber_from_string. Eventually that method may subsume the functionality
# of this one.
#
def read_ber syntax=nil
# don't bother with this line, since IO#getc by definition returns nil on eof.
#return nil if eof?
id = getc or return nil # 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
# Replaced the inject because it profiles hot.
#j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
j = 0
read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
[1 + (n & 127), j]
end end
end
newobj = read contentlength
class BerIdentifiedArray < Array
# This exceptionally clever and clear bit of code is verrrry slow. attr_accessor :ber_identifier
objtype = (syntax && syntax[id]) || BuiltinSyntax[id] def initialize
super
end
# == is expensive so sort this if/else so the common cases are at the top. end
obj = if objtype == :string
#(newobj || "").dup class BerIdentifiedNull
s = BerIdentifiedString.new( newobj || "" ) attr_accessor :ber_identifier
s.ber_identifier = id def to_ber
s "\005\000"
elsif objtype == :integer end
j = 0 end
newobj.each_byte {|b| j = (j << 8) + b}
j class BerIdentifiedOid
elsif objtype == :oid attr_accessor :ber_identifier
# cf X.690 pgh 8.19 for an explanation of this algorithm. def initialize oid
# Potentially not good enough. We may need a BerIdentifiedOid if oid.is_a?(String)
# as a subclass of BerIdentifiedArray, to get the ber identifier oid = oid.split(/\./).map {|s| s.to_i }
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0, f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :array
#seq = []
seq = BerIdentifiedArray.new
seq.ber_identifier = id
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 end
seq @value = oid
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
#raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
raise BerError.new( "unsupported object type: id=#{id}" )
end 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.
# Replaced this mechanism with subclasses because the instance_eval profiled too hot.
#obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
#obj.ber_identifier = id if obj.respond_to?(:ber_identifier)
obj
end
#--
# Violates DRY! This replicates the functionality of #read_ber.
# Eventually this method may replace that one.
# This version of #read_ber behaves properly in the face of incomplete
# data packets. If a full BER object is detected, we return an array containing
# the detected object and the number of bytes consumed from the string.
# If we don't detect a complete packet, return nil.
#
# Observe that weirdly we recursively call the original #read_ber in here.
# That needs to be fixed if we ever obsolete the original method in favor of this one.
def read_ber_from_string str, syntax=nil
id = str[0] or return nil
n = str[1] or return nil
n_consumed = 2
lengthlength,contentlength = if n <= 127
[1,n]
else
n1 = n & 127
return nil unless str.length >= (n_consumed + n1)
j = 0
n1.times {
j = (j << 8) + str[n_consumed]
n_consumed += 1
}
[1 + (n1), j]
end
return nil unless str.length >= (n_consumed + contentlength)
newobj = str[n_consumed...(n_consumed + contentlength)]
n_consumed += contentlength
objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
# == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :array
seq = BerIdentifiedArray.new
seq.ber_identifier = id
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!
# Also, we can use the standard read_ber method because
# we know for sure we have enough data. (Although this
# might be faster than the standard method.)
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
elsif objtype == :string
s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id
s
elsif objtype == :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0,f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
raise BerError.new( "unsupported object type: id=#{id}" )
end
[obj, n_consumed]
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
include Net::BER::BERParser
def read_ber syntax=nil
StringIO.new(self).read_ber(syntax)
end
def read_ber! syntax=nil
obj,n_consumed = read_ber_from_string(self, syntax)
if n_consumed
self.slice!(0...n_consumed)
obj
else
nil
end
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
"\002" + to_ber_internal
end
#
# to_ber_enumerated
#
def to_ber_enumerated
"\012" + to_ber_internal
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 end
# Generate a BER-encoding for an application-defined INTEGER.
# Example: SNMP's Counter, Gauge, and TimeTick types.
#
def to_ber_application tag
[0x40 + tag].pack("C") + to_ber_internal
end
#--
# Called internally to BER-encode the length and content bytes of a Fixnum.
# The caller will prepend the tag byte.
def to_ber_internal
# PLEASE optimize this code path. It's awfully ugly and probably slow.
# It also doesn't understand negative numbers yet.
raise Net::BER::BerError.new( "range error in fixnum" ) unless self >= 0
z = [self].pack("N")
zlen = if self < 0x80
1
elsif self < 0x8000
2
elsif self < 0x800000
3
else
4
end
[zlen].pack("C") + z[0-zlen,zlen]
end
private :to_ber_internal
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
# Ruby represents Bignums as two's-complement numbers so we may actually be
# good as far as representing negatives goes.
# I'm sure this implementation can be improved performance-wise if necessary.
# Ruby's Bignum#size returns the number of bytes in the internal representation
# of the number, but it can and will include leading zero bytes on at least
# some implementations. Evidently Ruby stores these as sets of quadbytes.
# It's not illegal in BER to encode all of the leading zeroes but let's strip
# them out anyway.
#
sz = self.size
out = "\000" * sz
(sz*8).times {|bit|
if self[bit] == 1
out[bit/8] += (1 << (bit % 8))
end
}
while out.length > 1 and out[-1] == 0
out.slice!(-1,1)
end
[2, out.length].pack("CC") + out.reverse
end
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
def to_ber_oid
ary = self.dup
first = ary.shift
raise Net::BER::BerError.new( "invalid OID" ) unless [0,1,2].include?(first)
first = first * 40 + ary.shift
ary.unshift first
oid = ary.pack("w*")
[6, oid.length].pack("CC") + oid
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

225
lib/net/ber/ber_parser.rb Normal file
View file

@ -0,0 +1,225 @@
module Net
module BER
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 = Net::BER.compile_syntax( {
:universal => {
:primitive => {
1 => :boolean,
2 => :integer,
4 => :string,
5 => :null,
6 => :oid,
10 => :integer,
13 => :string # (relative OID)
},
:constructed => {
16 => :array,
17 => :array
}
},
:context_specific => {
:primitive => {
10 => :integer
}
}
})
#
# 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.
#--
# BEWARE, this violates DRY and is largely equal in functionality to
# read_ber_from_string. Eventually that method may subsume the functionality
# of this one.
#
def read_ber syntax=nil
# don't bother with this line, since IO#getc by definition returns nil on eof.
#return nil if eof?
# here we'll create two different procs, one for 1.8 and one for 1.9
# the reason being getc doesn't return a byte value in 1.9, so we need to
# get the byte code out of the 1.9 encoded string
if RUBY_VERSION =~ /^1\.9/
fetch_byte = Proc.new { getc.bytes.first }
elsif RUBY_VERSION =~ /^1\.8/
fetch_byte = Proc.new { getc }
end
id = fetch_byte.call or return nil # 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 = fetch_byte.call
lengthlength,contentlength = if n <= 127
[1,n]
else
# Replaced the inject because it profiles hot.
#j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
j = 0
read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
[1 + (n & 127), j]
end
newobj = read contentlength
# This exceptionally clever and clear bit of code is verrrry slow.
objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
# == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :string
#(newobj || "").dup
s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id
s
elsif objtype == :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0, f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :array
#seq = []
seq = BerIdentifiedArray.new
seq.ber_identifier = id
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
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
#raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
raise BerError.new( "unsupported object type: id=#{id}" )
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.
# Replaced this mechanism with subclasses because the instance_eval profiled too hot.
#obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
#obj.ber_identifier = id if obj.respond_to?(:ber_identifier)
obj
end
#--
# Violates DRY! This replicates the functionality of #read_ber.
# Eventually this method may replace that one.
# This version of #read_ber behaves properly in the face of incomplete
# data packets. If a full BER object is detected, we return an array containing
# the detected object and the number of bytes consumed from the string.
# If we don't detect a complete packet, return nil.
#
# Observe that weirdly we recursively call the original #read_ber in here.
# That needs to be fixed if we ever obsolete the original method in favor of this one.
def read_ber_from_string str, syntax=nil
id = str[0] or return nil
n = str[1] or return nil
n_consumed = 2
lengthlength,contentlength = if n <= 127
[1,n]
else
n1 = n & 127
return nil unless str.length >= (n_consumed + n1)
j = 0
n1.times {
j = (j << 8) + str[n_consumed]
n_consumed += 1
}
[1 + (n1), j]
end
return nil unless str.length >= (n_consumed + contentlength)
newobj = str[n_consumed...(n_consumed + contentlength)]
n_consumed += contentlength
objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
# == is expensive so sort this if/else so the common cases are at the top.
obj = if objtype == :array
seq = BerIdentifiedArray.new
seq.ber_identifier = id
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!
# Also, we can use the standard read_ber method because
# we know for sure we have enough data. (Although this
# might be faster than the standard method.)
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
elsif objtype == :string
s = BerIdentifiedString.new( newobj || "" )
s.ber_identifier = id
s
elsif objtype == :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
elsif objtype == :oid
# cf X.690 pgh 8.19 for an explanation of this algorithm.
# Potentially not good enough. We may need a BerIdentifiedOid
# as a subclass of BerIdentifiedArray, to get the ber identifier
# and also a to_s method that produces the familiar dotted notation.
oid = newobj.unpack("w*")
f = oid.shift
g = if f < 40
[0,f]
elsif f < 80
[1, f-40]
else
[2, f-80] # f-80 can easily be > 80. What a weird optimization.
end
oid.unshift g.last
oid.unshift g.first
oid
elsif objtype == :boolean
newobj != "\000"
elsif objtype == :null
n = BerIdentifiedNull.new
n.ber_identifier = id
n
else
raise BerError.new( "unsupported object type: id=#{id}" )
end
[obj, n_consumed]
end
end
end
end

View file

@ -9,33 +9,20 @@
# #
# This program is free software. # This program is free software.
# You may re-distribute and/or modify this program under the same terms # You may re-distribute and/or modify this program under the same terms
# as Ruby itself: Ruby Distribution License or GNU General Public License. # as Ruby itself: Ruby Distribution License or GNU General Public License.x
# #
# #
# See Net::LDAP for documentation and usage samples. # 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/pdu'
require 'net/ldap/filter' require 'net/ldap/filter'
require 'net/ldap/dataset' require 'net/ldap/dataset'
require 'net/ldap/psw' require 'net/ldap/psw'
require 'net/ldap/entry' require 'net/ldap/entry'
module Net module Net
# == Net::LDAP # == Net::LDAP
# #
# This library provides a pure-Ruby implementation of the # This library provides a pure-Ruby implementation of the
@ -273,30 +260,30 @@ module Net
AsnSyntax = BER.compile_syntax({ AsnSyntax = BER.compile_syntax({
:application => { :application => {
:primitive => { :primitive => {
2 => :null # UnbindRequest body 2 => :null # UnbindRequest body
}, },
:constructed => { :constructed => {
0 => :array, # BindRequest 0 => :array, # BindRequest
1 => :array, # BindResponse 1 => :array, # BindResponse
2 => :array, # UnbindRequest 2 => :array, # UnbindRequest
3 => :array, # SearchRequest 3 => :array, # SearchRequest
4 => :array, # SearchData 4 => :array, # SearchData
5 => :array, # SearchResult 5 => :array, # SearchResult
6 => :array, # ModifyRequest 6 => :array, # ModifyRequest
7 => :array, # ModifyResponse 7 => :array, # ModifyResponse
8 => :array, # AddRequest 8 => :array, # AddRequest
9 => :array, # AddResponse 9 => :array, # AddResponse
10 => :array, # DelRequest 10 => :array, # DelRequest
11 => :array, # DelResponse 11 => :array, # DelResponse
12 => :array, # ModifyRdnRequest 12 => :array, # ModifyRdnRequest
13 => :array, # ModifyRdnResponse 13 => :array, # ModifyRdnResponse
14 => :array, # CompareRequest 14 => :array, # CompareRequest
15 => :array, # CompareResponse 15 => :array, # CompareResponse
16 => :array, # AbandonRequest 16 => :array, # AbandonRequest
19 => :array, # SearchResultReferral 19 => :array, # SearchResultReferral
24 => :array, # Unsolicited Notification 24 => :array, # Unsolicited Notification
} }
}, },
:context_specific => { :context_specific => {
:primitive => { :primitive => {
@ -745,7 +732,7 @@ module Net
# on it. Otherwise, connect, bind, and disconnect. # on it. Otherwise, connect, bind, and disconnect.
# The latter operation is obviously useful only as an auth check. # The latter operation is obviously useful only as an auth check.
# #
def bind auth=@auth def bind(auth=@auth)
if @open_connection if @open_connection
@result = @open_connection.bind auth @result = @open_connection.bind auth
else else
@ -1212,14 +1199,12 @@ module Net
def setup_encryption args def setup_encryption args
case args[:method] case args[:method]
when :simple_tls when :simple_tls
raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
ctx = OpenSSL::SSL::SSLContext.new ctx = OpenSSL::SSL::SSLContext.new
@conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx) @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx)
@conn.connect @conn.connect
@conn.sync_close = true @conn.sync_close = true
# additional branches requiring server validation and peer certs, etc. go here. # additional branches requiring server validation and peer certs, etc. go here.
when :start_tls when :start_tls
raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
msgid = next_msgid.to_ber msgid = next_msgid.to_ber
request = [StartTlsOid.to_ber].to_ber_appsequence( Net::LdapPdu::ExtendedRequest ) request = [StartTlsOid.to_ber].to_ber_appsequence( Net::LdapPdu::ExtendedRequest )
request_pkt = [msgid, request].to_ber_sequence request_pkt = [msgid, request].to_ber_sequence

View file

@ -0,0 +1,43 @@
require 'net/ldap/core_ext/array'
require 'net/ldap/core_ext/string'
require 'net/ldap/core_ext/bignum'
require 'net/ldap/core_ext/fixnum'
require 'net/ldap/core_ext/false_class'
require 'net/ldap/core_ext/true_class'
class Array
include Net::LDAP::Extensions::Array
end
class String
include Net::LDAP::Extensions::String
include Net::BER::BERParser
end
class Bignum
include Net::LDAP::Extensions::Bignum
end
class Fixnum
include Net::LDAP::Extensions::Fixnum
end
class FalseClass
include Net::LDAP::Extensions::FalseClass
end
class TrueClass
include Net::LDAP::Extensions::TrueClass
end
class IO
include Net::BER::BERParser
end
class StringIO
include Net::BER::BERParser
end
class OpenSSL::SSL::SSLSocket
include Net::BER::BERParser
end

View file

@ -0,0 +1,42 @@
module Net
class LDAP
module Extensions
module 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
def to_ber_oid
ary = self.dup
first = ary.shift
raise Net::BER::BerError.new( "invalid OID" ) unless [0,1,2].include?(first)
first = first * 40 + ary.shift
ary.unshift first
oid = ary.pack("w*")
[6, oid.length].pack("CC") + oid
end
private
def to_ber_seq_internal code
s = self.to_s
[code].pack('C') + s.length.to_ber_length_encoding + s
end
end
end
end
end # class Array

View file

@ -0,0 +1,38 @@
module Net
class LDAP
module Extensions
module 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
# Ruby represents Bignums as two's-complement numbers so we may actually be
# good as far as representing negatives goes.
# I'm sure this implementation can be improved performance-wise if necessary.
# Ruby's Bignum#size returns the number of bytes in the internal representation
# of the number, but it can and will include leading zero bytes on at least
# some implementations. Evidently Ruby stores these as sets of quadbytes.
# It's not illegal in BER to encode all of the leading zeroes but let's strip
# them out anyway.
#
sz = self.size
out = "\000" * sz
(sz*8).times {|bit|
if self[bit] == 1
out[bit/8] += (1 << (bit % 8))
end
}
while out.length > 1 and out[-1] == 0
out.slice!(-1,1)
end
[2, out.length].pack("CC") + out.reverse
end
end
end
end
end

View file

@ -0,0 +1,11 @@
module Net
class LDAP
module Extensions
module FalseClass
def to_ber
"\001\001\000"
end
end
end
end
end

View file

@ -0,0 +1,52 @@
module Net
class LDAP
module Extensions
module Fixnum
def to_ber
"\002" + to_ber_internal
end
def to_ber_enumerated
"\012" + to_ber_internal
end
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
# Generate a BER-encoding for an application-defined INTEGER.
# Example: SNMP's Counter, Gauge, and TimeTick types.
#
def to_ber_application tag
[0x40 + tag].pack("C") + to_ber_internal
end
#--
# Called internally to BER-encode the length and content bytes of a Fixnum.
# The caller will prepend the tag byte.
def to_ber_internal
# PLEASE optimize this code path. It's awfully ugly and probably slow.
# It also doesn't understand negative numbers yet.
raise Net::BER::BerError.new( "range error in fixnum" ) unless self >= 0
z = [self].pack("N")
zlen = if self < 0x80
1
elsif self < 0x8000
2
elsif self < 0x800000
3
else
4
end
[zlen].pack("C") + z[0-zlen,zlen]
end
private :to_ber_internal
end
end
end
end

View file

@ -0,0 +1,48 @@
module Net
class LDAP
module Extensions
module 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
def read_ber syntax=nil
StringIO.new(self).read_ber(syntax)
end
def read_ber! syntax=nil
obj,n_consumed = read_ber_from_string(self, syntax)
if n_consumed
self.slice!(0...n_consumed)
obj
else
nil
end
end
end
end
end
end

View file

@ -0,0 +1,11 @@
module Net
class LDAP
module Extensions
module TrueClass
def to_ber
"\001\001\001"
end
end
end
end
end

View file

@ -26,10 +26,6 @@
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# #
require 'base64'
module Net module Net
class LDAP class LDAP

View file

@ -408,7 +408,6 @@ class FilterParser #:nodoc:
attr_reader :filter attr_reader :filter
def initialize str def initialize str
require 'strscan'
@filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" ) @filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
end end

View file

@ -43,10 +43,8 @@ class Password
def generate( type, str ) def generate( type, str )
case type case type
when :md5 when :md5
require 'md5'
"{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }" "{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }"
when :sha when :sha
require 'sha1'
"{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }" "{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }"
# when ssha # when ssha
else else

View file

@ -27,13 +27,8 @@
# THIS FILE IS A STUB. # THIS FILE IS A STUB.
module Net module Net
class LDIF class LDIF
end
end
end # class LDIF
end # module Net

View file

@ -26,9 +26,6 @@
# #
# #
require 'net/ber'
module Net module Net
class SNMP class SNMP

View file

@ -1,45 +0,0 @@
require 'rdoc/rdoc'
##
# Build the rdoc documentation. Also, try to build the RI documentation.
#
def build_rdoc(options)
RDoc::RDoc.new.document(options)
rescue RDoc::RDocError => e
$stderr.puts e.message
rescue Exception => e
$stderr.puts "Couldn't build RDoc documentation\n#{e.message}"
end
def build_ri(files)
RDoc::RDoc.new.document(["--ri-site", "--merge"] + files)
rescue RDoc::RDocError => e
$stderr.puts e.message
rescue Exception => e
$stderr.puts "Couldn't build Ri documentation\n#{e.message}"
end
def run_tests(test_list)
return if test_list.empty?
require 'test/unit/ui/console/testrunner'
$:.unshift "lib"
test_list.each do |test|
next if File.directory?(test)
require test
end
tests = []
ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) }
tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) }
tests.delete_if { |o| o == Test::Unit::TestCase }
tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) }
$:.shift
end
rdoc = %w(--main README.txt --line-numbers)
ri = %w(--ri-site --merge)
dox = %w(README.txt History.txt lib)
build_rdoc rdoc + dox
build_ri ri + dox
#run_tests Dir["test/**/*"]

1366
setup.rb

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
Most of the tests here have been migrated to ../test
where all new tests will be created. These have been
left here for now just for reference and will me
migrated as well.
These will not be run automatically by rake.

View file

@ -1,190 +0,0 @@
# $Id$
#
#
$:.unshift "lib"
require 'test/unit'
require 'net/ldap'
require 'stringio'
class TestLdapClient < Test::Unit::TestCase
# TODO: these tests crash and burn if the associated
# LDAP testserver isn't up and running.
# We rely on being able to read a file with test data
# in LDIF format.
# TODO, WARNING: for the moment, this data is in a file
# whose name and location are HARDCODED into the
# instance method load_test_data.
def setup
@host = "127.0.0.1"
@port = 3890
@auth = {
:method => :simple,
:username => "cn=bigshot,dc=bayshorenetworks,dc=com",
:password => "opensesame"
}
@ldif = load_test_data
end
# Get some test data which will be used to validate
# the responses from the test LDAP server we will
# connect to.
# TODO, Bogus: we are HARDCODING the location of the file for now.
#
def load_test_data
ary = File.readlines( "tests/testdata.ldif" )
hash = {}
while line = ary.shift and line.chomp!
if line =~ /^dn:[\s]*/i
dn = $'
hash[dn] = {}
while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/
hash[dn][$1.downcase.intern] ||= []
hash[dn][$1.downcase.intern] << $'
end
end
end
hash
end
# Binding tests.
# Need tests for all kinds of network failures and incorrect auth.
# TODO: Implement a class-level timeout for operations like bind.
# Search has a timeout defined at the protocol level, other ops do not.
# TODO, use constants for the LDAP result codes, rather than hardcoding them.
def test_bind
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert_equal( true, ldap.bind )
assert_equal( 0, ldap.get_operation_result.code )
assert_equal( "Success", ldap.get_operation_result.message )
bad_username = @auth.merge( {:username => "cn=badguy,dc=imposters,dc=com"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_username
assert_equal( false, ldap.bind )
assert_equal( 48, ldap.get_operation_result.code )
assert_equal( "Inappropriate Authentication", ldap.get_operation_result.message )
bad_password = @auth.merge( {:password => "cornhusk"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_password
assert_equal( false, ldap.bind )
assert_equal( 49, ldap.get_operation_result.code )
assert_equal( "Invalid Credentials", ldap.get_operation_result.message )
end
def test_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {:base => "dc=smalldomain,dc=com"}
assert_equal( false, ldap.search( search ))
assert_equal( 32, ldap.get_operation_result.code )
search = {:base => "dc=bayshorenetworks,dc=com"}
assert_equal( true, ldap.search( search ))
assert_equal( 0, ldap.get_operation_result.code )
ldap.search( search ) {|res|
assert_equal( res, @ldif )
}
end
# This is a helper routine for test_search_attributes.
def internal_test_search_attributes attrs_to_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert( ldap.bind )
search = {
:base => "dc=bayshorenetworks,dc=com",
:attributes => attrs_to_search
}
ldif = @ldif
ldif.each {|dn,entry|
entry.delete_if {|attr,value|
! attrs_to_search.include?(attr)
}
}
assert_equal( true, ldap.search( search ))
ldap.search( search ) {|res|
res_keys = res.keys.sort
ldif_keys = ldif.keys.sort
assert( res_keys, ldif_keys )
res.keys.each {|rk|
assert( res[rk], ldif[rk] )
}
}
end
def test_search_attributes
internal_test_search_attributes [:mail]
internal_test_search_attributes [:cn]
internal_test_search_attributes [:ou]
internal_test_search_attributes [:hasaccessprivilege]
internal_test_search_attributes ["mail"]
internal_test_search_attributes ["cn"]
internal_test_search_attributes ["ou"]
internal_test_search_attributes ["hasaccessrole"]
internal_test_search_attributes [:mail, :cn, :ou, :hasaccessrole]
internal_test_search_attributes [:mail, "cn", :ou, "hasaccessrole"]
end
def test_search_filters
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {
:base => "dc=bayshorenetworks,dc=com",
:filter => Net::LDAP::Filter.eq( "sn", "Fosse" )
}
ldap.search( search ) {|res|
p res
}
end
def test_open
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
ldap.open {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
def test_ldap_open
Net::LDAP.open( :host => @host, :port => @port, :auth => @auth ) {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
end