From d37c3b3ae6f84210b7f84896ba143ccc0796f458 Mon Sep 17 00:00:00 2001 From: Rory OConnell Date: Tue, 9 Feb 2010 16:46:49 -0600 Subject: [PATCH] Cleaned up the layout quite a bit to make Gemifying and including into Rails 3 less painful. Some steps to 1.9 compatibility --- Manifest.txt | 37 - Release-Announcement | 95 -- lib/net.rb | 38 + lib/net/ber.rb | 583 ++--------- lib/net/ber/ber_parser.rb | 225 +++++ lib/net/ldap.rb | 67 +- lib/net/ldap/core_ext/all.rb | 43 + lib/net/ldap/core_ext/array.rb | 42 + lib/net/ldap/core_ext/bignum.rb | 38 + lib/net/ldap/core_ext/false_class.rb | 11 + lib/net/ldap/core_ext/fixnum.rb | 52 + lib/net/ldap/core_ext/string.rb | 48 + lib/net/ldap/core_ext/true_class.rb | 11 + lib/net/ldap/entry.rb | 4 - lib/net/ldap/filter.rb | 1 - lib/net/ldap/psw.rb | 2 - lib/net/ldif.rb | 9 +- lib/net/snmp.rb | 3 - pre-setup.rb | 45 - setup.rb | 1366 -------------------------- tests/NOTICE.txt | 6 - tests/testldap.rb | 190 ---- 22 files changed, 603 insertions(+), 2313 deletions(-) delete mode 100644 Manifest.txt delete mode 100644 Release-Announcement create mode 100644 lib/net.rb create mode 100644 lib/net/ber/ber_parser.rb create mode 100644 lib/net/ldap/core_ext/all.rb create mode 100644 lib/net/ldap/core_ext/array.rb create mode 100644 lib/net/ldap/core_ext/bignum.rb create mode 100644 lib/net/ldap/core_ext/false_class.rb create mode 100644 lib/net/ldap/core_ext/fixnum.rb create mode 100644 lib/net/ldap/core_ext/string.rb create mode 100644 lib/net/ldap/core_ext/true_class.rb delete mode 100644 pre-setup.rb delete mode 100644 setup.rb delete mode 100644 tests/NOTICE.txt delete mode 100644 tests/testldap.rb diff --git a/Manifest.txt b/Manifest.txt deleted file mode 100644 index 64fcda6..0000000 --- a/Manifest.txt +++ /dev/null @@ -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 diff --git a/Release-Announcement b/Release-Announcement deleted file mode 100644 index 5d14811..0000000 --- a/Release-Announcement +++ /dev/null @@ -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." - diff --git a/lib/net.rb b/lib/net.rb new file mode 100644 index 0000000..d8f7574 --- /dev/null +++ b/lib/net.rb @@ -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' \ No newline at end of file diff --git a/lib/net/ber.rb b/lib/net/ber.rb index dbe2fbd..4ecf3fa 100644 --- a/lib/net/ber.rb +++ b/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 - - diff --git a/lib/net/ber/ber_parser.rb b/lib/net/ber/ber_parser.rb new file mode 100644 index 0000000..264cd66 --- /dev/null +++ b/lib/net/ber/ber_parser.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 5d0ffeb..16d591e 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -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 diff --git a/lib/net/ldap/core_ext/all.rb b/lib/net/ldap/core_ext/all.rb new file mode 100644 index 0000000..5be52e5 --- /dev/null +++ b/lib/net/ldap/core_ext/all.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap/core_ext/array.rb b/lib/net/ldap/core_ext/array.rb new file mode 100644 index 0000000..ff88c9f --- /dev/null +++ b/lib/net/ldap/core_ext/array.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap/core_ext/bignum.rb b/lib/net/ldap/core_ext/bignum.rb new file mode 100644 index 0000000..6decc88 --- /dev/null +++ b/lib/net/ldap/core_ext/bignum.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap/core_ext/false_class.rb b/lib/net/ldap/core_ext/false_class.rb new file mode 100644 index 0000000..79f921d --- /dev/null +++ b/lib/net/ldap/core_ext/false_class.rb @@ -0,0 +1,11 @@ +module Net + class LDAP + module Extensions + module FalseClass + def to_ber + "\001\001\000" + end + end + end + end +end \ No newline at end of file diff --git a/lib/net/ldap/core_ext/fixnum.rb b/lib/net/ldap/core_ext/fixnum.rb new file mode 100644 index 0000000..2a899e5 --- /dev/null +++ b/lib/net/ldap/core_ext/fixnum.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap/core_ext/string.rb b/lib/net/ldap/core_ext/string.rb new file mode 100644 index 0000000..f1c0b77 --- /dev/null +++ b/lib/net/ldap/core_ext/string.rb @@ -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 \ No newline at end of file diff --git a/lib/net/ldap/core_ext/true_class.rb b/lib/net/ldap/core_ext/true_class.rb new file mode 100644 index 0000000..5456f9f --- /dev/null +++ b/lib/net/ldap/core_ext/true_class.rb @@ -0,0 +1,11 @@ +module Net + class LDAP + module Extensions + module TrueClass + def to_ber + "\001\001\001" + end + end + end + end +end \ No newline at end of file diff --git a/lib/net/ldap/entry.rb b/lib/net/ldap/entry.rb index 1291d8e..65ebdb3 100644 --- a/lib/net/ldap/entry.rb +++ b/lib/net/ldap/entry.rb @@ -26,10 +26,6 @@ #--------------------------------------------------------------------------- # - -require 'base64' - - module Net class LDAP diff --git a/lib/net/ldap/filter.rb b/lib/net/ldap/filter.rb index a3f5899..63829e8 100644 --- a/lib/net/ldap/filter.rb +++ b/lib/net/ldap/filter.rb @@ -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 diff --git a/lib/net/ldap/psw.rb b/lib/net/ldap/psw.rb index b4f0442..01c4dbb 100644 --- a/lib/net/ldap/psw.rb +++ b/lib/net/ldap/psw.rb @@ -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 diff --git a/lib/net/ldif.rb b/lib/net/ldif.rb index be95bcd..a7b8db2 100644 --- a/lib/net/ldif.rb +++ b/lib/net/ldif.rb @@ -27,13 +27,8 @@ # THIS FILE IS A STUB. module Net - class LDIF - - - end # class LDIF - - -end # module Net + end +end diff --git a/lib/net/snmp.rb b/lib/net/snmp.rb index f74f265..1074b8e 100644 --- a/lib/net/snmp.rb +++ b/lib/net/snmp.rb @@ -26,9 +26,6 @@ # # -require 'net/ber' - - module Net class SNMP diff --git a/pre-setup.rb b/pre-setup.rb deleted file mode 100644 index 8f88d75..0000000 --- a/pre-setup.rb +++ /dev/null @@ -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/**/*"] diff --git a/setup.rb b/setup.rb deleted file mode 100644 index 0673386..0000000 --- a/setup.rb +++ /dev/null @@ -1,1366 +0,0 @@ -# -# setup.rb -# -# Copyright (c) 2000-2004 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU LGPL, Lesser General Public License version 2.1. -# - -unless Enumerable.method_defined?(:map) # Ruby 1.4.6 - module Enumerable - alias map collect - end -end - -unless File.respond_to?(:read) # Ruby 1.6 - def File.read(fname) - open(fname) {|f| - return f.read - } - end -end - -def File.binread(fname) - open(fname, 'rb') {|f| - return f.read - } -end - -# for corrupted windows stat(2) -def File.dir?(path) - File.directory?((path[-1,1] == '/') ? path : path + '/') -end - - -class SetupError < StandardError; end - -def setup_rb_error(msg) - raise SetupError, msg -end - -# -# Config -# - -if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } - ARGV.delete(arg) - require arg.split(/=/, 2)[1] - $".push 'rbconfig.rb' -else - require 'rbconfig' -end - -def multipackage_install? - FileTest.directory?(File.dirname($0) + '/packages') -end - - -class ConfigItem - def initialize(name, template, default, desc) - @name = name.freeze - @template = template - @value = default - @default = default.dup.freeze - @description = desc - end - - attr_reader :name - attr_reader :description - - attr_accessor :default - alias help_default default - - def help_opt - "--#{@name}=#{@template}" - end - - def value - @value - end - - def eval(table) - @value.gsub(%r<\$([^/]+)>) { table[$1] } - end - - def set(val) - @value = check(val) - end - - private - - def check(val) - setup_rb_error "config: --#{name} requires argument" unless val - val - end -end - -class BoolItem < ConfigItem - def config_type - 'bool' - end - - def help_opt - "--#{@name}" - end - - private - - def check(val) - return 'yes' unless val - unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val - setup_rb_error "config: --#{@name} accepts only yes/no for argument" - end - (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' - end -end - -class PathItem < ConfigItem - def config_type - 'path' - end - - private - - def check(path) - setup_rb_error "config: --#{@name} requires argument" unless path - path[0,1] == '$' ? path : File.expand_path(path) - end -end - -class ProgramItem < ConfigItem - def config_type - 'program' - end -end - -class SelectItem < ConfigItem - def initialize(name, template, default, desc) - super - @ok = template.split('/') - end - - def config_type - 'select' - end - - private - - def check(val) - unless @ok.include?(val.strip) - setup_rb_error "config: use --#{@name}=#{@template} (#{val})" - end - val.strip - end -end - -class PackageSelectionItem < ConfigItem - def initialize(name, template, default, help_default, desc) - super name, template, default, desc - @help_default = help_default - end - - attr_reader :help_default - - def config_type - 'package' - end - - private - - def check(val) - unless File.dir?("packages/#{val}") - setup_rb_error "config: no such package: #{val}" - end - val - end -end - -class ConfigTable_class - - def initialize(items) - @items = items - @table = {} - items.each do |i| - @table[i.name] = i - end - ALIASES.each do |ali, name| - @table[ali] = @table[name] - end - @script_extensions = ['rb'] - end - - attr_accessor :script_extensions - - include Enumerable - - def each(&block) - @items.each(&block) - end - - def key?(name) - @table.key?(name) - end - - def lookup(name) - @table[name] or raise ArgumentError, "no such config item: #{name}" - end - - def add(item) - @items.push item - @table[item.name] = item - end - - def remove(name) - item = lookup(name) - @items.delete_if {|i| i.name == name } - @table.delete_if {|name, i| i.name == name } - item - end - - def new - dup() - end - - def savefile - '.config' - end - - def load - begin - t = dup() - File.foreach(savefile()) do |line| - k, v = *line.split(/=/, 2) - t[k] = v.strip - end - t - rescue Errno::ENOENT - setup_rb_error $!.message + "#{File.basename($0)} config first" - end - end - - def save - @items.each {|i| i.value } - File.open(savefile(), 'w') {|f| - @items.each do |i| - f.printf "%s=%s\n", i.name, i.value if i.value - end - } - end - - def [](key) - lookup(key).eval(self) - end - - def []=(key, val) - lookup(key).set val - end - -end - -c = ::Config::CONFIG - -rubypath = c['bindir'] + '/' + c['ruby_install_name'] - -major = c['MAJOR'].to_i -minor = c['MINOR'].to_i -teeny = c['TEENY'].to_i -version = "#{major}.#{minor}" - -# ruby ver. >= 1.4.4? -newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - -if c['rubylibdir'] - # V < 1.6.3 - _stdruby = c['rubylibdir'] - _siteruby = c['sitedir'] - _siterubyver = c['sitelibdir'] - _siterubyverarch = c['sitearchdir'] -elsif newpath_p - # 1.4.4 <= V <= 1.6.3 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = c['sitedir'] - _siterubyver = "$siteruby/#{version}" - _siterubyverarch = "$siterubyver/#{c['arch']}" -else - # V < 1.4.4 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" - _siterubyver = _siteruby - _siterubyverarch = "$siterubyver/#{c['arch']}" -end -libdir = '-* dummy libdir *-' -stdruby = '-* dummy rubylibdir *-' -siteruby = '-* dummy site_ruby *-' -siterubyver = '-* dummy site_ruby version *-' -parameterize = lambda {|path| - path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ - .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ - .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ - .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ - .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') -} -libdir = parameterize.call(c['libdir']) -stdruby = parameterize.call(_stdruby) -siteruby = parameterize.call(_siteruby) -siterubyver = parameterize.call(_siterubyver) -siterubyverarch = parameterize.call(_siterubyverarch) - -if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } - makeprog = arg.sub(/'/, '').split(/=/, 2)[1] -else - makeprog = 'make' -end - -common_conf = [ - PathItem.new('prefix', 'path', c['prefix'], - 'path prefix of target environment'), - PathItem.new('bindir', 'path', parameterize.call(c['bindir']), - 'the directory for commands'), - PathItem.new('libdir', 'path', libdir, - 'the directory for libraries'), - PathItem.new('datadir', 'path', parameterize.call(c['datadir']), - 'the directory for shared data'), - PathItem.new('mandir', 'path', parameterize.call(c['mandir']), - 'the directory for man pages'), - PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), - 'the directory for man pages'), - PathItem.new('stdruby', 'path', stdruby, - 'the directory for standard ruby libraries'), - PathItem.new('siteruby', 'path', siteruby, - 'the directory for version-independent aux ruby libraries'), - PathItem.new('siterubyver', 'path', siterubyver, - 'the directory for aux ruby libraries'), - PathItem.new('siterubyverarch', 'path', siterubyverarch, - 'the directory for aux ruby binaries'), - PathItem.new('rbdir', 'path', '$siterubyver', - 'the directory for ruby scripts'), - PathItem.new('sodir', 'path', '$siterubyverarch', - 'the directory for ruby extentions'), - PathItem.new('rubypath', 'path', rubypath, - 'the path to set to #! line'), - ProgramItem.new('rubyprog', 'name', rubypath, - 'the ruby program using for installation'), - ProgramItem.new('makeprog', 'name', makeprog, - 'the make program to compile ruby extentions'), - SelectItem.new('shebang', 'all/ruby/never', 'ruby', - 'shebang line (#!) editing mode'), - BoolItem.new('without-ext', 'yes/no', 'no', - 'does not compile/install ruby extentions') -] -class ConfigTable_class # open again - ALIASES = { - 'std-ruby' => 'stdruby', - 'site-ruby-common' => 'siteruby', # For backward compatibility - 'site-ruby' => 'siterubyver', # For backward compatibility - 'bin-dir' => 'bindir', - 'bin-dir' => 'bindir', - 'rb-dir' => 'rbdir', - 'so-dir' => 'sodir', - 'data-dir' => 'datadir', - 'ruby-path' => 'rubypath', - 'ruby-prog' => 'rubyprog', - 'ruby' => 'rubyprog', - 'make-prog' => 'makeprog', - 'make' => 'makeprog' - } -end -multipackage_conf = [ - PackageSelectionItem.new('with', 'name,name...', '', 'ALL', - 'package names that you want to install'), - PackageSelectionItem.new('without', 'name,name...', '', 'NONE', - 'package names that you do not want to install') -] -if multipackage_install? - ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) -else - ConfigTable = ConfigTable_class.new(common_conf) -end - - -module MetaConfigAPI - - def eval_file_ifexist(fname) - instance_eval File.read(fname), fname, 1 if File.file?(fname) - end - - def config_names - ConfigTable.map {|i| i.name } - end - - def config?(name) - ConfigTable.key?(name) - end - - def bool_config?(name) - ConfigTable.lookup(name).config_type == 'bool' - end - - def path_config?(name) - ConfigTable.lookup(name).config_type == 'path' - end - - def value_config?(name) - case ConfigTable.lookup(name).config_type - when 'bool', 'path' - true - else - false - end - end - - def add_config(item) - ConfigTable.add item - end - - def add_bool_config(name, default, desc) - ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) - end - - def add_path_config(name, default, desc) - ConfigTable.add PathItem.new(name, 'path', default, desc) - end - - def set_config_default(name, default) - ConfigTable.lookup(name).default = default - end - - def remove_config(name) - ConfigTable.remove(name) - end - - def add_script_extension(ext) - ConfigTable.script_extensions << ext - end -end - - -# -# File Operations -# - -module FileOperations - - def mkdir_p(dirname, prefix = nil) - dirname = prefix + File.expand_path(dirname) if prefix - $stderr.puts "mkdir -p #{dirname}" if verbose? - return if no_harm? - - # does not check '/'... it's too abnormal case - dirs = File.expand_path(dirname).split(%r<(?=/)>) - if /\A[a-z]:\z/i =~ dirs[0] - disk = dirs.shift - dirs[0] = disk + dirs[0] - end - dirs.each_index do |idx| - path = dirs[0..idx].join('') - Dir.mkdir path unless File.dir?(path) - end - end - - def rm_f(fname) - $stderr.puts "rm -f #{fname}" if verbose? - return if no_harm? - - if File.exist?(fname) or File.symlink?(fname) - File.chmod 0777, fname - File.unlink fname - end - end - - def rm_rf(dn) - $stderr.puts "rm -rf #{dn}" if verbose? - return if no_harm? - - Dir.chdir dn - Dir.foreach('.') do |fn| - next if fn == '.' - next if fn == '..' - if File.dir?(fn) - verbose_off { - rm_rf fn - } - else - verbose_off { - rm_f fn - } - end - end - Dir.chdir '..' - Dir.rmdir dn - end - - def move_file(src, dest) - File.unlink dest if File.exist?(dest) - begin - File.rename src, dest - rescue - File.open(dest, 'wb') {|f| f.write File.binread(src) } - File.chmod File.stat(src).mode, dest - File.unlink src - end - end - - def install(from, dest, mode, prefix = nil) - $stderr.puts "install #{from} #{dest}" if verbose? - return if no_harm? - - realdest = prefix ? prefix + File.expand_path(dest) : dest - realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) - str = File.binread(from) - if diff?(str, realdest) - verbose_off { - rm_f realdest if File.exist?(realdest) - } - File.open(realdest, 'wb') {|f| - f.write str - } - File.chmod mode, realdest - - File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| - if prefix - f.puts realdest.sub(prefix, '') - else - f.puts realdest - end - } - end - end - - def diff?(new_content, path) - return true unless File.exist?(path) - new_content != File.binread(path) - end - - def command(str) - $stderr.puts str if verbose? - system str or raise RuntimeError, "'system #{str}' failed" - end - - def ruby(str) - command config('rubyprog') + ' ' + str - end - - def make(task = '') - command config('makeprog') + ' ' + task - end - - def extdir?(dir) - File.exist?(dir + '/MANIFEST') - end - - def all_files_in(dirname) - Dir.open(dirname) {|d| - return d.select {|ent| File.file?("#{dirname}/#{ent}") } - } - end - - REJECT_DIRS = %w( - CVS SCCS RCS CVS.adm .svn - ) - - def all_dirs_in(dirname) - Dir.open(dirname) {|d| - return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS - } - end - -end - - -# -# Main Installer -# - -module HookUtils - - def run_hook(name) - try_run_hook "#{curr_srcdir()}/#{name}" or - try_run_hook "#{curr_srcdir()}/#{name}.rb" - end - - def try_run_hook(fname) - return false unless File.file?(fname) - begin - instance_eval File.read(fname), fname, 1 - rescue - setup_rb_error "hook #{fname} failed:\n" + $!.message - end - true - end - -end - - -module HookScriptAPI - - def get_config(key) - @config[key] - end - - alias config get_config - - def set_config(key, val) - @config[key] = val - end - - # - # srcdir/objdir (works only in the package directory) - # - - #abstract srcdir_root - #abstract objdir_root - #abstract relpath - - def curr_srcdir - "#{srcdir_root()}/#{relpath()}" - end - - def curr_objdir - "#{objdir_root()}/#{relpath()}" - end - - def srcfile(path) - "#{curr_srcdir()}/#{path}" - end - - def srcexist?(path) - File.exist?(srcfile(path)) - end - - def srcdirectory?(path) - File.dir?(srcfile(path)) - end - - def srcfile?(path) - File.file? srcfile(path) - end - - def srcentries(path = '.') - Dir.open("#{curr_srcdir()}/#{path}") {|d| - return d.to_a - %w(. ..) - } - end - - def srcfiles(path = '.') - srcentries(path).select {|fname| - File.file?(File.join(curr_srcdir(), path, fname)) - } - end - - def srcdirectories(path = '.') - srcentries(path).select {|fname| - File.dir?(File.join(curr_srcdir(), path, fname)) - } - end - -end - - -class ToplevelInstaller - - Version = '3.3.1' - Copyright = 'Copyright (c) 2000-2004 Minero Aoki' - - TASKS = [ - [ 'all', 'do config, setup, then install' ], - [ 'config', 'saves your configurations' ], - [ 'show', 'shows current configuration' ], - [ 'setup', 'compiles ruby extentions and others' ], - [ 'install', 'installs files' ], - [ 'clean', "does `make clean' for each extention" ], - [ 'distclean',"does `make distclean' for each extention" ] - ] - - def ToplevelInstaller.invoke - instance().invoke - end - - @singleton = nil - - def ToplevelInstaller.instance - @singleton ||= new(File.dirname($0)) - @singleton - end - - include MetaConfigAPI - - def initialize(ardir_root) - @config = nil - @options = { 'verbose' => true } - @ardir = File.expand_path(ardir_root) - end - - def inspect - "#<#{self.class} #{__id__()}>" - end - - def invoke - run_metaconfigs - case task = parsearg_global() - when nil, 'all' - @config = load_config('config') - parsearg_config - init_installers - exec_config - exec_setup - exec_install - else - @config = load_config(task) - __send__ "parsearg_#{task}" - init_installers - __send__ "exec_#{task}" - end - end - - def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" - end - - def load_config(task) - case task - when 'config' - ConfigTable.new - when 'clean', 'distclean' - if File.exist?(ConfigTable.savefile) - then ConfigTable.load - else ConfigTable.new - end - else - ConfigTable.load - end - end - - def init_installers - @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) - end - - # - # Hook Script API bases - # - - def srcdir_root - @ardir - end - - def objdir_root - '.' - end - - def relpath - '.' - end - - # - # Option Parsing - # - - def parsearg_global - valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ - - while arg = ARGV.shift - case arg - when /\A\w+\z/ - setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg - return arg - - when '-q', '--quiet' - @options['verbose'] = false - - when '--verbose' - @options['verbose'] = true - - when '-h', '--help' - print_usage $stdout - exit 0 - - when '-v', '--version' - puts "#{File.basename($0)} version #{Version}" - exit 0 - - when '--copyright' - puts Copyright - exit 0 - - else - setup_rb_error "unknown global option '#{arg}'" - end - end - - nil - end - - - def parsearg_no_options - unless ARGV.empty? - setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" - end - end - - alias parsearg_show parsearg_no_options - alias parsearg_setup parsearg_no_options - alias parsearg_clean parsearg_no_options - alias parsearg_distclean parsearg_no_options - - def parsearg_config - re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ - @options['config-opt'] = [] - - while i = ARGV.shift - if /\A--?\z/ =~ i - @options['config-opt'] = ARGV.dup - break - end - m = re.match(i) or setup_rb_error "config: unknown option #{i}" - name, value = *m.to_a[1,2] - @config[name] = value - end - end - - def parsearg_install - @options['no-harm'] = false - @options['install-prefix'] = '' - while a = ARGV.shift - case a - when /\A--no-harm\z/ - @options['no-harm'] = true - when /\A--prefix=(.*)\z/ - path = $1 - path = File.expand_path(path) unless path[0,1] == '/' - @options['install-prefix'] = path - else - setup_rb_error "install: unknown option #{a}" - end - end - end - - def print_usage(out) - out.puts 'Typical Installation Procedure:' - out.puts " $ ruby #{File.basename $0} config" - out.puts " $ ruby #{File.basename $0} setup" - out.puts " # ruby #{File.basename $0} install (may require root privilege)" - out.puts - out.puts 'Detailed Usage:' - out.puts " ruby #{File.basename $0} " - out.puts " ruby #{File.basename $0} [] []" - - fmt = " %-24s %s\n" - out.puts - out.puts 'Global options:' - out.printf fmt, '-q,--quiet', 'suppress message outputs' - out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, '-h,--help', 'print this message' - out.printf fmt, '-v,--version', 'print version and quit' - out.printf fmt, ' --copyright', 'print copyright and quit' - out.puts - out.puts 'Tasks:' - TASKS.each do |name, desc| - out.printf fmt, name, desc - end - - fmt = " %-24s %s [%s]\n" - out.puts - out.puts 'Options for CONFIG or ALL:' - ConfigTable.each do |item| - out.printf fmt, item.help_opt, item.description, item.help_default - end - out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" - out.puts - out.puts 'Options for INSTALL:' - out.printf fmt, '--no-harm', 'only display what to do if given', 'off' - out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' - out.puts - end - - # - # Task Handlers - # - - def exec_config - @installer.exec_config - @config.save # must be final - end - - def exec_setup - @installer.exec_setup - end - - def exec_install - @installer.exec_install - end - - def exec_show - ConfigTable.each do |i| - printf "%-20s %s\n", i.name, i.value - end - end - - def exec_clean - @installer.exec_clean - end - - def exec_distclean - @installer.exec_distclean - end - -end - - -class ToplevelInstallerMulti < ToplevelInstaller - - include HookUtils - include HookScriptAPI - include FileOperations - - def initialize(ardir) - super - @packages = all_dirs_in("#{@ardir}/packages") - raise 'no package exists' if @packages.empty? - end - - def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" - @packages.each do |name| - eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" - end - end - - def init_installers - @installers = {} - @packages.each do |pack| - @installers[pack] = Installer.new(@config, @options, - "#{@ardir}/packages/#{pack}", - "packages/#{pack}") - end - - with = extract_selection(config('with')) - without = extract_selection(config('without')) - @selected = @installers.keys.select {|name| - (with.empty? or with.include?(name)) \ - and not without.include?(name) - } - end - - def extract_selection(list) - a = list.split(/,/) - a.each do |name| - setup_rb_error "no such package: #{name}" unless @installers.key?(name) - end - a - end - - def print_usage(f) - super - f.puts 'Inluded packages:' - f.puts ' ' + @packages.sort.join(' ') - f.puts - end - - # - # multi-package metaconfig API - # - - attr_reader :packages - - def declare_packages(list) - raise 'package list is empty' if list.empty? - list.each do |name| - raise "directory packages/#{name} does not exist"\ - unless File.dir?("#{@ardir}/packages/#{name}") - end - @packages = list - end - - # - # Task Handlers - # - - def exec_config - run_hook 'pre-config' - each_selected_installers {|inst| inst.exec_config } - run_hook 'post-config' - @config.save # must be final - end - - def exec_setup - run_hook 'pre-setup' - each_selected_installers {|inst| inst.exec_setup } - run_hook 'post-setup' - end - - def exec_install - run_hook 'pre-install' - each_selected_installers {|inst| inst.exec_install } - run_hook 'post-install' - end - - def exec_clean - rm_f ConfigTable.savefile - run_hook 'pre-clean' - each_selected_installers {|inst| inst.exec_clean } - run_hook 'post-clean' - end - - def exec_distclean - rm_f ConfigTable.savefile - run_hook 'pre-distclean' - each_selected_installers {|inst| inst.exec_distclean } - run_hook 'post-distclean' - end - - # - # lib - # - - def each_selected_installers - Dir.mkdir 'packages' unless File.dir?('packages') - @selected.each do |pack| - $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] - Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") - Dir.chdir "packages/#{pack}" - yield @installers[pack] - Dir.chdir '../..' - end - end - - def verbose? - @options['verbose'] - end - - def no_harm? - @options['no-harm'] - end - -end - - -class Installer - - FILETYPES = %w( bin lib ext data ) - - include HookScriptAPI - include HookUtils - include FileOperations - - def initialize(config, opt, srcroot, objroot) - @config = config - @options = opt - @srcdir = File.expand_path(srcroot) - @objdir = File.expand_path(objroot) - @currdir = '.' - end - - def inspect - "#<#{self.class} #{File.basename(@srcdir)}>" - end - - # - # Hook Script API base methods - # - - def srcdir_root - @srcdir - end - - def objdir_root - @objdir - end - - def relpath - @currdir - end - - # - # configs/options - # - - def no_harm? - @options['no-harm'] - end - - def verbose? - @options['verbose'] - end - - def verbose_off - begin - save, @options['verbose'] = @options['verbose'], false - yield - ensure - @options['verbose'] = save - end - end - - # - # TASK config - # - - def exec_config - exec_task_traverse 'config' - end - - def config_dir_bin(rel) - end - - def config_dir_lib(rel) - end - - def config_dir_ext(rel) - extconf if extdir?(curr_srcdir()) - end - - def extconf - opt = @options['config-opt'].join(' ') - command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" - end - - def config_dir_data(rel) - end - - # - # TASK setup - # - - def exec_setup - exec_task_traverse 'setup' - end - - def setup_dir_bin(rel) - all_files_in(curr_srcdir()).each do |fname| - adjust_shebang "#{curr_srcdir()}/#{fname}" - end - end - - def adjust_shebang(path) - return if no_harm? - tmpfile = File.basename(path) + '.tmp' - begin - File.open(path, 'rb') {|r| - first = r.gets - return unless File.basename(config('rubypath')) == 'ruby' - return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' - $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? - File.open(tmpfile, 'wb') {|w| - w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) - w.write r.read - } - move_file tmpfile, File.basename(path) - } - ensure - File.unlink tmpfile if File.exist?(tmpfile) - end - end - - def setup_dir_lib(rel) - end - - def setup_dir_ext(rel) - make if extdir?(curr_srcdir()) - end - - def setup_dir_data(rel) - end - - # - # TASK install - # - - def exec_install - rm_f 'InstalledFiles' - exec_task_traverse 'install' - end - - def install_dir_bin(rel) - install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 - end - - def install_dir_lib(rel) - install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 - end - - def install_dir_ext(rel) - return unless extdir?(curr_srcdir()) - install_files ruby_extentions('.'), - "#{config('sodir')}/#{File.dirname(rel)}", - 0555 - end - - def install_dir_data(rel) - install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644 - end - - def install_files(list, dest, mode) - mkdir_p dest, @options['install-prefix'] - list.each do |fname| - install fname, dest, mode, @options['install-prefix'] - end - end - - def ruby_scripts - collect_filenames_auto().select {|n| /\.(#{ConfigTable.script_extensions.join('|')})\z/ =~ n } - end - - # picked up many entries from cvs-1.11.1/src/ignore.c - reject_patterns = %w( - core RCSLOG tags TAGS .make.state - .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb - *~ *.old *.bak *.BAK *.orig *.rej _$* *$ - - *.org *.in .* - ) - mapping = { - '.' => '\.', - '$' => '\$', - '#' => '\#', - '*' => '.*' - } - REJECT_PATTERNS = Regexp.new('\A(?:' + - reject_patterns.map {|pat| - pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } - }.join('|') + - ')\z') - - def collect_filenames_auto - mapdir((existfiles() - hookfiles()).reject {|fname| - REJECT_PATTERNS =~ fname - }) - end - - def existfiles - all_files_in(curr_srcdir()) | all_files_in('.') - end - - def hookfiles - %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| - %w( config setup install clean ).map {|t| sprintf(fmt, t) } - }.flatten - end - - def mapdir(filelist) - filelist.map {|fname| - if File.exist?(fname) # objdir - fname - else # srcdir - File.join(curr_srcdir(), fname) - end - } - end - - def ruby_extentions(dir) - Dir.open(dir) {|d| - ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } - if ents.empty? - setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" - end - return ents - } - end - - # - # TASK clean - # - - def exec_clean - exec_task_traverse 'clean' - rm_f ConfigTable.savefile - rm_f 'InstalledFiles' - end - - def clean_dir_bin(rel) - end - - def clean_dir_lib(rel) - end - - def clean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'clean' if File.file?('Makefile') - end - - def clean_dir_data(rel) - end - - # - # TASK distclean - # - - def exec_distclean - exec_task_traverse 'distclean' - rm_f ConfigTable.savefile - rm_f 'InstalledFiles' - end - - def distclean_dir_bin(rel) - end - - def distclean_dir_lib(rel) - end - - def distclean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'distclean' if File.file?('Makefile') - end - - # - # lib - # - - def exec_task_traverse(task) - run_hook "pre-#{task}" - FILETYPES.each do |type| - if config('without-ext') == 'yes' and type == 'ext' - $stderr.puts 'skipping ext/* by user option' if verbose? - next - end - traverse task, type, "#{task}_dir_#{type}" - end - run_hook "post-#{task}" - end - - def traverse(task, rel, mid) - dive_into(rel) { - run_hook "pre-#{task}" - __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') - all_dirs_in(curr_srcdir()).each do |d| - traverse task, "#{rel}/#{d}", mid - end - run_hook "post-#{task}" - } - end - - def dive_into(rel) - return unless File.dir?("#{@srcdir}/#{rel}") - - dir = File.basename(rel) - Dir.mkdir dir unless File.dir?(dir) - prevdir = Dir.pwd - Dir.chdir dir - $stderr.puts '---> ' + rel if verbose? - @currdir = rel - yield - Dir.chdir prevdir - $stderr.puts '<--- ' + rel if verbose? - @currdir = File.dirname(rel) - end - -end - - -if $0 == __FILE__ - begin - if multipackage_install? - ToplevelInstallerMulti.invoke - else - ToplevelInstaller.invoke - end - rescue SetupError - raise if $DEBUG - $stderr.puts $!.message - $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." - exit 1 - end -end diff --git a/tests/NOTICE.txt b/tests/NOTICE.txt deleted file mode 100644 index 709c0d3..0000000 --- a/tests/NOTICE.txt +++ /dev/null @@ -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. diff --git a/tests/testldap.rb b/tests/testldap.rb deleted file mode 100644 index a401476..0000000 --- a/tests/testldap.rb +++ /dev/null @@ -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 - -