From c46c93777ee23e5b98f6a32ecfa0380bd0ad9560 Mon Sep 17 00:00:00 2001 From: "David J. Lee" Date: Fri, 24 Feb 2012 09:55:17 -0600 Subject: [PATCH 1/3] Added a new filter type bineq that will create an equality filter and NOT force convert data to UTF-8. This is required for proper binary data filters in Microsoft Active Directory. --- Contributors.rdoc | 1 + lib/net/ber/core_ext/string.rb | 8 ++++++++ lib/net/ldap/filter.rb | 24 +++++++++++++++++++++++- spec/unit/ber/ber_spec.rb | 5 +++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Contributors.rdoc b/Contributors.rdoc index a169b5b..bef012a 100644 --- a/Contributors.rdoc +++ b/Contributors.rdoc @@ -19,3 +19,4 @@ Contributions since: * Derek Harmel (derekharmel) * Erik Hetzner (egh) * nowhereman +* David J. Lee (DavidJLee) diff --git a/lib/net/ber/core_ext/string.rb b/lib/net/ber/core_ext/string.rb index 28aeedd..876bc79 100644 --- a/lib/net/ber/core_ext/string.rb +++ b/lib/net/ber/core_ext/string.rb @@ -16,6 +16,14 @@ module Net::BER::Extensions::String [code].pack('C') + raw_string.length.to_ber_length_encoding + raw_string end + ## + # Converts a string to a BER string but does *not* encode to UTF-8 first. + # This is required for proper representation of binary data for Microsoft + # Active Directory + def to_ber_bin(code = 0x04) + [code].pack('C') + length.to_ber_length_encoding + self + end + def raw_utf8_encoded if self.respond_to?(:encode) # Strings should be UTF-8 encoded according to LDAP. diff --git a/lib/net/ldap/filter.rb b/lib/net/ldap/filter.rb index 660684f..9b6828c 100644 --- a/lib/net/ldap/filter.rb +++ b/lib/net/ldap/filter.rb @@ -23,7 +23,7 @@ class Net::LDAP::Filter ## # Known filter types. - FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex ] + FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq ] def initialize(op, left, right) #:nodoc: unless FilterTypes.include?(op) @@ -65,6 +65,23 @@ class Net::LDAP::Filter new(:eq, attribute, value) end + ## + # Creates a Filter object indicating a binary comparison. + # this prevents the search data from being forced into a UTF-8 string. + # + # This is primarily used for Microsoft Active Directory to compare + # GUID values. + # + # # for guid represented as hex charecters + # guid = "6a31b4a12aa27a41aca9603f27dd5116" + # guid_bin = [guid].pack("H*") + # f = Net::LDAP::Filter.bineq("objectGUID", guid_bin) + # + # This filter does not perform any escaping. + def bineq(attribute, value) + new(:bineq, attribute, value) + end + ## # Creates a Filter object indicating extensible comparison. This Filter # object is currently considered EXPERIMENTAL. @@ -399,6 +416,8 @@ class Net::LDAP::Filter "!(#{@left}=#{@right})" when :eq "#{@left}=#{@right}" + when :bineq + "#{@left}=#{@right}" when :ex "#{@left}:=#{@right}" when :ge @@ -508,6 +527,9 @@ class Net::LDAP::Filter else # equality [@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(3) end + when :bineq + # make sure data is not forced to UTF-8 + [@left.to_s.to_ber, unescape(@right).to_ber_bin].to_ber_contextspecific(3) when :ex seq = [] diff --git a/spec/unit/ber/ber_spec.rb b/spec/unit/ber/ber_spec.rb index 86dc48a..10288d0 100644 --- a/spec/unit/ber/ber_spec.rb +++ b/spec/unit/ber/ber_spec.rb @@ -84,6 +84,11 @@ describe "BER encoding of" do it "should properly encode strings encodable as UTF-8" do "teststring".encode("US-ASCII").to_ber.should == "\x04\nteststring" end + it "should properly encode binary data strings using to_ber_bin" do + # This is used for searching for GUIDs in Active Directory + ["6a31b4a12aa27a41aca9603f27dd5116"].pack("H*").to_ber_bin.should == + "\x04\x10" + "j1\xB4\xA1*\xA2zA\xAC\xA9`?'\xDDQ\x16" + end it "should fail on strings that can not be converted to UTF-8" do error = Encoding::UndefinedConversionError lambda {"\x81".to_ber }.should raise_exception(error) From 9a9d5f074231b1f2b037c279a3e1e66b9c459cc0 Mon Sep 17 00:00:00 2001 From: "David J. Lee" Date: Fri, 24 Feb 2012 14:01:53 -0600 Subject: [PATCH 2/3] Added configuration option to force paged searches off --- lib/net/ldap.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 0c79b92..d9ad70c 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -308,6 +308,7 @@ class Net::LDAP DefaultPort = 389 DefaultAuth = { :method => :anonymous } DefaultTreebase = "dc=com" + DefaultForceNoPage = false StartTlsOid = "1.3.6.1.4.1.1466.20037" @@ -370,6 +371,8 @@ class Net::LDAP # specifying the Hash {:method => :simple_tls}. There is a fairly large # range of potential values that may be given for this parameter. See # #encryption for details. + # * :force_no_page => Set to true to prevent paged results even if your + # server says it supports them. This is a fix for MS Active Directory # # Instantiating a Net::LDAP object does not result in network # traffic to the LDAP server. It simply stores the connection and binding @@ -380,6 +383,7 @@ class Net::LDAP @verbose = false # Make this configurable with a switch on the class. @auth = args[:auth] || DefaultAuth @base = args[:base] || DefaultTreebase + @force_no_page = args[:force_no_page] || DefaultForceNoPage encryption args[:encryption] # may be nil if pr = @auth[:password] and pr.respond_to?(:call) @@ -1092,6 +1096,10 @@ class Net::LDAP # MUST refactor the root_dse call out. #++ def paged_searches_supported? + # active directory returns that it supports paged results. However + # it returns binary data in the rfc2696_cookie which throws an + # encoding exception breaking searching. + return false if @force_no_page @server_caps ||= search_root_dse @server_caps[:supportedcontrol].include?(Net::LDAP::LdapControls::PagedResults) end @@ -1387,6 +1395,10 @@ class Net::LDAP::Connection #:nodoc: search_attributes.to_ber_sequence ].to_ber_appsequence(3) + # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory + # this breaks when calling to_ber. (Can't force binary data to UTF-8) + # we have to disable paging (even though server supports it) to get around this... + controls = [] controls << [ From 9fa0b982b713e50a9fa0429a42cfe1d874ceb2bb Mon Sep 17 00:00:00 2001 From: "David J. Lee" Date: Fri, 24 Feb 2012 15:19:18 -0600 Subject: [PATCH 3/3] Added parens around args for rename. --- lib/net/ldap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index d9ad70c..e0e9cab 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -1542,7 +1542,7 @@ class Net::LDAP::Connection #:nodoc: #-- # TODO: need to support a time limit, in case the server fails to respond. #++ - def rename args + def rename(args) old_dn = args[:olddn] or raise "Unable to rename empty DN" new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN" delete_attrs = args[:delete_attributes] ? true : false