diff --git a/History.txt b/History.txt index 8cc3481..8eeb081 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,10 @@ +=== Net::LDAP NEXT / 2010-__-__ +* Moved the core class extensions extensions from being in the Net::LDAP + hierarchy to the Net::BER hierarchy as most of the methods therein are + related to BER-encoding values. This will make extracting Net::BER from + Net::LDAP easier in the future. +* Documented the core class extension methods. + === Net::LDAP 0.1.1 / 2010-03-18 * Fixing a critical problem with sockets. diff --git a/lib/net/ber.rb b/lib/net/ber.rb index d4f0ce7..26d01b4 100644 --- a/lib/net/ber.rb +++ b/lib/net/ber.rb @@ -67,6 +67,10 @@ end module Net module BER + # Used for BER-encoding the length and content bytes of a Fixnum integer + # values. + MAX_FIXNUM_SIZE = 0.size + class BerError < StandardError; end class BerIdentifiedString < String @@ -92,4 +96,4 @@ module Net end end -require 'net/ber/ber_parser' +require 'net/ber/core_ext' diff --git a/lib/net/ber/core_ext.rb b/lib/net/ber/core_ext.rb new file mode 100644 index 0000000..94da38e --- /dev/null +++ b/lib/net/ber/core_ext.rb @@ -0,0 +1,72 @@ +# NET::BER +# Mixes ASN.1/BER convenience methods into several standard classes. Also +# provides BER parsing functionality. +# +#-- +# Copyright (C) 2006 by Francis Cianfrocca and other contributors. All +# Rights Reserved. +# +# Gmail: garbagecat10 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +#++ + +require 'net/ber/ber_parser' +class IO + include Net::BER::BERParser +end + +class StringIO + include Net::BER::BERParser +end + +if defined? ::OpenSSL + class OpenSSL::SSL::SSLSocket + include Net::BER::BERParser + end +end + +module Net::BER::Extensions; end + +require 'net/ber/core_ext/string' +class String + include Net::BER::BERParser + include Net::BER::Extensions::String +end + +require 'net/ber/core_ext/array' +class Array + include Net::BER::Extensions::Array +end + +require 'net/ber/core_ext/bignum' +class Bignum + include Net::BER::Extensions::Bignum +end + +require 'net/ber/core_ext/fixnum' +class Fixnum + include Net::BER::Extensions::Fixnum +end + +require 'net/ber/core_ext/true_class' +class TrueClass + include Net::BER::Extensions::TrueClass +end + +require 'net/ber/core_ext/false_class' +class FalseClass + include Net::BER::Extensions::FalseClass +end diff --git a/lib/net/ber/core_ext/array.rb b/lib/net/ber/core_ext/array.rb new file mode 100644 index 0000000..4696a5a --- /dev/null +++ b/lib/net/ber/core_ext/array.rb @@ -0,0 +1,79 @@ +module Net::BER::Extensions::Array + ## + # Converts an Array to a BER sequence. All values in the Array are + # expected to be in BER format prior to calling this method. + def to_ber(id = 0) + # The universal sequence tag 0x30 is composed of the base tag value + # (0x10) and the constructed flag (0x20). + to_ber_seq_internal(0x30 + id) + end + alias_method :to_ber_sequence, :to_ber + + ## + # Converts an Array to a BER set. All values in the Array are expected to + # be in BER format prior to calling this method. + def to_ber_set(id = 0) + # The universal set tag 0x31 is composed of the base tag value (0x11) + # and the constructed flag (0x20). + to_ber_seq_internal(0x31 + id) + end + + ## + # Converts an Array to an application-specific sequence, assigned a tag + # value that is meaningful to the particular protocol being used. All + # values in the Array are expected to be in BER format pr prior to calling + # this method. + #-- + # Implementor's note 20100320(AZ): RFC 4511 (the LDAPv3 protocol) as well + # as earlier RFCs 1777 and 2559 seem to indicate that LDAP only has + # application constructed sequences (0x60). However, ldapsearch sends some + # context-specific constructed sequences (0xA0); other clients may do the + # same. This behaviour appears to violate the RFCs. In real-world + # practice, we may need to change calls of #to_ber_appsequence to + # #to_ber_contextspecific for full LDAP server compatibility. + # + # This note probably belongs elsewhere. + #++ + def to_ber_appsequence(id = 0) + # The application sequence tag always starts from the application flag + # (0x40) and the constructed flag (0x20). + to_ber_seq_internal(0x60 + id) + end + + ## + # Converts an Array to a context-specific sequence, assigned a tag value + # that is meaningful to the particular context of the particular protocol + # being used. All values in the Array are expected to be in BER format + # prior to calling this method. + def to_ber_contextspecific(id = 0) + # The application sequence tag always starts from the context flag + # (0x80) and the constructed flag (0x20). + to_ber_seq_internal(0xa0 + id) + end + + ## + # The internal sequence packing routine. All values in the Array are + # expected to be in BER format prior to calling this method. + def to_ber_seq_internal(code) + s = self.join + [code].pack('C') + s.length.to_ber_length_encoding + s + end + private :to_ber_seq_internal + + ## + # SNMP Object Identifiers (OID) are special arrays + #-- + # 20100320 AZ: I do not think that this method should be in BER, since + # this appears to be SNMP-specific. This should probably be subsumed by a + # proper SNMP OID object. + #++ + def to_ber_oid + ary = self.dup + first = ary.shift + raise Net::BER::BerError, "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 diff --git a/lib/net/ber/core_ext/bignum.rb b/lib/net/ber/core_ext/bignum.rb new file mode 100644 index 0000000..b1d9a6f --- /dev/null +++ b/lib/net/ber/core_ext/bignum.rb @@ -0,0 +1,19 @@ +module Net::BER::Extensions::Bignum + ## + # Converts a Bignum to an uncompressed BER integer. + def to_ber + result = [] + + # NOTE: Array#pack's 'w' is a BER _compressed_ integer. We need + # uncompressed BER integers, so we're not using that. See also: + # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/228864 + n = self + while n > 0 + b = n & 0xff + result << b + n = n >> 8 + end + + "\002" + ([result.size] + result.reverse).pack('C*') + end +end diff --git a/lib/net/ber/core_ext/false_class.rb b/lib/net/ber/core_ext/false_class.rb new file mode 100644 index 0000000..bdbbca9 --- /dev/null +++ b/lib/net/ber/core_ext/false_class.rb @@ -0,0 +1,7 @@ +module Net::BER::Extensions::FalseClass + ## + # Converts +false+ to the BER wireline representation of +false+. + def to_ber + "\001\001\000" + end +end diff --git a/lib/net/ber/core_ext/fixnum.rb b/lib/net/ber/core_ext/fixnum.rb new file mode 100644 index 0000000..5d16354 --- /dev/null +++ b/lib/net/ber/core_ext/fixnum.rb @@ -0,0 +1,63 @@ +module Net::BER::Extensions::Fixnum + ## + # Converts the fixnum to BER format. + def to_ber + "\002#{to_ber_internal}" + end + + ## + # Converts the fixnum to BER enumerated format. + def to_ber_enumerated + "\012#{to_ber_internal}" + end + + ## + # Converts the fixnum to BER length encodining format. + 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. Examples of + # such integers are SNMP's Counter, Gauge, and TimeTick types. + def to_ber_application(tag) + [0x40 + tag].pack("C") + to_ber_internal + end + + ## + # Used to BER-encode the length and content bytes of a Fixnum. Callers + # must prepend the tag byte for the contained value. + def to_ber_internal + # CAUTION: Bit twiddling ahead. You might want to shield your eyes or + # something. + + # Looks for the first byte in the fixnum that is not all zeroes. It does + # this by masking one byte after another, checking the result for bits + # that are left on. + size = Net::BER::MAX_FIXNUM_SIZE + while size > 1 + break if (self & (0xff << (size - 1) * 8)) > 0 + size -= 1 + end + + # Store the size of the fixnum in the result + result = [size] + + # Appends bytes to result, starting with higher orders first. Extraction + # of bytes is done by right shifting the original fixnum by an amount + # and then masking that with 0xff. + while size > 0 + # right shift size - 1 bytes, mask with 0xff + result << ((self >> ((size - 1) * 8)) & 0xff) + size -= 1 + end + + result.pack('C*') + end + private :to_ber_internal +end diff --git a/lib/net/ber/core_ext/string.rb b/lib/net/ber/core_ext/string.rb new file mode 100644 index 0000000..8646543 --- /dev/null +++ b/lib/net/ber/core_ext/string.rb @@ -0,0 +1,51 @@ +require 'stringio' + +module Net::BER::Extensions::String + ## + # Converts a string to a BER string. Universal octet-strings are tagged + # with 0x04, but other values are possible depending on the context, so we + # let the caller give us one. + # + # User code should call either #to_ber_application_string or + # #to_ber_contextspecific. + def to_ber(code = 0x04) + [code].pack('C') + length.to_ber_length_encoding + self + end + + ## + # Creates an application-specific BER string encoded value with the + # provided syntax code value. + def to_ber_application_string(code) + to_ber(0x40 + code) + end + + ## + # Creates a context-specific BER string encoded value with the provided + # syntax code value. + def to_ber_contextspecific(code) + to_ber(0x80 + code) + end + + ## + # Nondestructively reads a BER object from this string. + def read_ber(syntax = nil) + StringIO.new(self).read_ber(syntax) + end + +=begin + # 20100319 AZ I've kept this here because I'm not yet sure if it's + # necessary. + + ## + # Destructively reads a BER object from this string. + def read_ber!(syntax = nil) + obj, consumed = read_ber_from_string(self, syntax) + if consumed + self.slice!(0...consumed) + obj + else + nil + end + end +=end +end diff --git a/lib/net/ber/core_ext/true_class.rb b/lib/net/ber/core_ext/true_class.rb new file mode 100644 index 0000000..16ef9da --- /dev/null +++ b/lib/net/ber/core_ext/true_class.rb @@ -0,0 +1,9 @@ +module Net::BER::Extensions::TrueClass + ## + # Converts +true+ to the BER wireline representation of +true+. + def to_ber + # 20100319 AZ: Note that this may not be the completely correct value, + # per some test documentation. We need to determine the truth of this. + "\001\001\001" + end +end diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 13657a9..bbf71b5 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -8,7 +8,6 @@ require 'net/ldap/filter' require 'net/ldap/dataset' require 'net/ldap/psw' require 'net/ldap/entry' -require 'net/ldap/core_ext/all' module Net # == Net::LDAP diff --git a/lib/net/ldap/core_ext/all.rb b/lib/net/ldap/core_ext/all.rb deleted file mode 100644 index 3aa1ba6..0000000 --- a/lib/net/ldap/core_ext/all.rb +++ /dev/null @@ -1,43 +0,0 @@ -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::BER::BERParser - include Net::LDAP::Extensions::String -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 diff --git a/lib/net/ldap/core_ext/array.rb b/lib/net/ldap/core_ext/array.rb deleted file mode 100644 index e2ad1e0..0000000 --- a/lib/net/ldap/core_ext/array.rb +++ /dev/null @@ -1,42 +0,0 @@ -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.join - [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 deleted file mode 100644 index 7eb8214..0000000 --- a/lib/net/ldap/core_ext/bignum.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Net - class LDAP - module Extensions - module Bignum - - def to_ber - # NOTE: Array#pack's 'w' is a BER _compressed_ integer. We need - # uncompressed BER integers, so we're not using that. See also: - # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/228864 - result = [] - - n = self - while n>0 - b = n & 0xff - result << b - n = n >> 8 - end - - "\002" + ([result.size] + result.reverse).pack('C*') - 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 deleted file mode 100644 index 79f921d..0000000 --- a/lib/net/ldap/core_ext/false_class.rb +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 349ea87..0000000 --- a/lib/net/ldap/core_ext/fixnum.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Net - class LDAP - module Extensions - module 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. - # - MAX_SIZE = 0.size - def to_ber_internal - # CAUTION: Bit twiddling ahead. You might want to shield your eyes - # or something. - - # Looks for the first byte in the fixnum that is not all zeroes. It - # does this by masking one byte after another, checking the result - # for bits that are left on. - size = MAX_SIZE - while size>1 - break if (self & (0xff << (size-1)*8)) > 0 - size -= 1 - end - - # Store the size of the fixnum in the result - result = [size] - - # Appends bytes to result, starting with higher orders first. - # Extraction of bytes is done by right shifting the original fixnum - # by an amount and then masking that with 0xff. - while size>0 - # right shift size-1 bytes, mask with 0xff - result << ((self >> ((size-1)*8)) & 0xff) - size -= 1 - end - - result.pack('C*') - 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 deleted file mode 100644 index b1d170d..0000000 --- a/lib/net/ldap/core_ext/string.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'stringio' - -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 - 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 deleted file mode 100644 index 5456f9f..0000000 --- a/lib/net/ldap/core_ext/true_class.rb +++ /dev/null @@ -1,11 +0,0 @@ -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