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:
parent
ba08042d75
commit
d37c3b3ae6
22 changed files with 603 additions and 2313 deletions
37
Manifest.txt
37
Manifest.txt
|
@ -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
|
|
@ -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
38
lib/net.rb
Normal 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'
|
583
lib/net/ber.rb
583
lib/net/ber.rb
|
@ -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 BER
|
||||
|
||||
class BerError < StandardError; end
|
||||
|
||||
|
||||
class BerIdentifiedString < String
|
||||
attr_accessor :ber_identifier
|
||||
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]
|
||||
class BerError < StandardError; end
|
||||
|
||||
class BerIdentifiedString < String
|
||||
attr_accessor :ber_identifier
|
||||
def initialize args
|
||||
super args
|
||||
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
|
||||
|
||||
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
|
||||
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}" )
|
||||
@value = oid
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
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
225
lib/net/ber/ber_parser.rb
Normal 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
|
|
@ -9,33 +9,20 @@
|
|||
#
|
||||
# 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.
|
||||
# as Ruby itself: Ruby Distribution License or GNU General Public License.x
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
@ -273,30 +260,30 @@ module Net
|
|||
|
||||
AsnSyntax = BER.compile_syntax({
|
||||
:application => {
|
||||
:primitive => {
|
||||
2 => :null # UnbindRequest body
|
||||
},
|
||||
: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
|
||||
}
|
||||
:primitive => {
|
||||
2 => :null # UnbindRequest body
|
||||
},
|
||||
: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 => {
|
||||
|
@ -745,7 +732,7 @@ module Net
|
|||
# on it. Otherwise, connect, bind, and disconnect.
|
||||
# The latter operation is obviously useful only as an auth check.
|
||||
#
|
||||
def bind auth=@auth
|
||||
def bind(auth=@auth)
|
||||
if @open_connection
|
||||
@result = @open_connection.bind auth
|
||||
else
|
||||
|
@ -1212,14 +1199,12 @@ module Net
|
|||
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.
|
||||
when :start_tls
|
||||
raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available
|
||||
msgid = next_msgid.to_ber
|
||||
request = [StartTlsOid.to_ber].to_ber_appsequence( Net::LdapPdu::ExtendedRequest )
|
||||
request_pkt = [msgid, request].to_ber_sequence
|
||||
|
|
43
lib/net/ldap/core_ext/all.rb
Normal file
43
lib/net/ldap/core_ext/all.rb
Normal 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
|
42
lib/net/ldap/core_ext/array.rb
Normal file
42
lib/net/ldap/core_ext/array.rb
Normal 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
|
38
lib/net/ldap/core_ext/bignum.rb
Normal file
38
lib/net/ldap/core_ext/bignum.rb
Normal 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
|
11
lib/net/ldap/core_ext/false_class.rb
Normal file
11
lib/net/ldap/core_ext/false_class.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Net
|
||||
class LDAP
|
||||
module Extensions
|
||||
module FalseClass
|
||||
def to_ber
|
||||
"\001\001\000"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
52
lib/net/ldap/core_ext/fixnum.rb
Normal file
52
lib/net/ldap/core_ext/fixnum.rb
Normal 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
|
48
lib/net/ldap/core_ext/string.rb
Normal file
48
lib/net/ldap/core_ext/string.rb
Normal 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
|
11
lib/net/ldap/core_ext/true_class.rb
Normal file
11
lib/net/ldap/core_ext/true_class.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Net
|
||||
class LDAP
|
||||
module Extensions
|
||||
module TrueClass
|
||||
def to_ber
|
||||
"\001\001\001"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,10 +26,6 @@
|
|||
#---------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
|
||||
require 'base64'
|
||||
|
||||
|
||||
module Net
|
||||
class LDAP
|
||||
|
||||
|
|
|
@ -408,7 +408,6 @@ 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
|
||||
|
||||
|
|
|
@ -43,10 +43,8 @@ class Password
|
|||
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
|
||||
|
|
|
@ -27,13 +27,8 @@
|
|||
# THIS FILE IS A STUB.
|
||||
|
||||
module Net
|
||||
|
||||
class LDIF
|
||||
|
||||
|
||||
end # class LDIF
|
||||
|
||||
|
||||
end # module Net
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -26,9 +26,6 @@
|
|||
#
|
||||
#
|
||||
|
||||
require 'net/ber'
|
||||
|
||||
|
||||
module Net
|
||||
|
||||
class SNMP
|
||||
|
|
45
pre-setup.rb
45
pre-setup.rb
|
@ -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/**/*"]
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
Loading…
Reference in a new issue