lib/net/ldap/connection.rb in net-ldap-0.11 vs lib/net/ldap/connection.rb in net-ldap-0.12.0
- old
+ new
@@ -7,27 +7,46 @@
MaxSaslChallenges = 10
def initialize(server)
@instrumentation_service = server[:instrumentation_service]
- begin
- @conn = server[:socket] || TCPSocket.new(server[:host], server[:port])
- rescue SocketError
- raise Net::LDAP::Error, "No such address or other socket error."
- rescue Errno::ECONNREFUSED
- raise Net::LDAP::Error, "Server #{server[:host]} refused connection on port #{server[:port]}."
- rescue Errno::EHOSTUNREACH => error
- raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})"
- rescue Errno::ETIMEDOUT
- raise Net::LDAP::Error, "Connection to #{server[:host]} timed out."
+ if server[:socket]
+ prepare_socket(server)
+ else
+ server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
+ open_connection(server)
end
- if server[:encryption]
- setup_encryption server[:encryption]
+ yield self if block_given?
+ end
+
+ def prepare_socket(server)
+ socket = server[:socket]
+ encryption = server[:encryption]
+
+ @conn = socket
+ setup_encryption encryption if encryption
+ end
+
+ def open_connection(server)
+ hosts = server[:hosts]
+ encryption = server[:encryption]
+
+ errors = []
+ hosts.each do |host, port|
+ begin
+ prepare_socket(server.merge(socket: TCPSocket.new(host, port)))
+ return
+ rescue Net::LDAP::Error, SocketError, SystemCallError,
+ OpenSSL::SSL::SSLError => e
+ # Ensure the connection is closed in the event a setup failure.
+ close
+ errors << [e, host, port]
+ end
end
- yield self if block_given?
+ raise Net::LDAP::ConnectionError.new(errors)
end
module GetbyteForSSLSocket
def getbyte
getc.ord
@@ -61,22 +80,22 @@
conn
end
#--
- # Helper method called only from new, and only after we have a
- # successfully-opened @conn instance variable, which is a TCP connection.
- # Depending on the received arguments, we establish SSL, potentially
- # replacing the value of @conn accordingly. Don't generate any errors here
- # if no encryption is requested. DO raise Net::LDAP::Error objects if encryption
- # is requested and we have trouble setting it up. That includes if OpenSSL
- # is not set up on the machine. (Question: how does the Ruby OpenSSL
- # wrapper react in that case?) DO NOT filter exceptions raised by the
- # OpenSSL library. Let them pass back to the user. That should make it
- # easier for us to debug the problem reports. Presumably (hopefully?) that
- # will also produce recognizable errors if someone tries to use this on a
- # machine without OpenSSL.
+ # Helper method called only from prepare_socket or open_connection, and only
+ # after we have a successfully-opened @conn instance variable, which is a TCP
+ # connection. Depending on the received arguments, we establish SSL,
+ # potentially replacing the value of @conn accordingly. Don't generate any
+ # errors here if no encryption is requested. DO raise Net::LDAP::Error objects
+ # if encryption is requested and we have trouble setting it up. That includes
+ # if OpenSSL is not set up on the machine. (Question: how does the Ruby
+ # OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
+ # OpenSSL library. Let them pass back to the user. That should make it easier
+ # for us to debug the problem reports. Presumably (hopefully?) that will also
+ # produce recognizable errors if someone tries to use this on a machine
+ # without OpenSSL.
#
# The simple_tls method is intended as the simplest, stupidest, easiest
# solution for people who want nothing more than encrypted comms with the
# LDAP server. It doesn't do any server-cert validation and requires
# nothing in the way of key files and root-cert files, etc etc. OBSERVE:
@@ -122,10 +141,11 @@
# This is provided as a convenience method to make sure a connection
# object gets closed without waiting for a GC to happen. Clients shouldn't
# have to call it, but perhaps it will come in handy someday.
#++
def close
+ return if @conn.nil?
@conn.close
@conn = nil
end
# Internal: Reads messages by ID from a queue, falling back to reading from
@@ -216,135 +236,16 @@
end
def bind(auth)
instrument "bind.net_ldap_connection" do |payload|
payload[:method] = meth = auth[:method]
- if [:simple, :anonymous, :anon].include?(meth)
- bind_simple auth
- elsif meth == :sasl
- bind_sasl(auth)
- elsif meth == :gss_spnego
- bind_gss_spnego(auth)
- else
- raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})"
- end
+ adapter = Net::LDAP::AuthAdapter[meth]
+ adapter.new(self).bind(auth)
end
end
#--
- # Implements a simple user/psw authentication. Accessed by calling #bind
- # with a method of :simple or :anonymous.
- #++
- def bind_simple(auth)
- user, psw = if auth[:method] == :simple
- [auth[:username] || auth[:dn], auth[:password]]
- else
- ["", ""]
- end
-
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
-
- message_id = next_msgid
- request = [
- LdapVersion.to_ber, user.to_ber,
- psw.to_ber_contextspecific(0)
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
-
- write(request, nil, message_id)
- pdu = queued_read(message_id)
-
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
- raise Net::LDAP::NoBindResultError, "no bind result"
- end
-
- pdu
- end
-
- #--
- # Required parameters: :mechanism, :initial_credential and
- # :challenge_response
- #
- # Mechanism is a string value that will be passed in the SASL-packet's
- # "mechanism" field.
- #
- # Initial credential is most likely a string. It's passed in the initial
- # BindRequest that goes to the server. In some protocols, it may be empty.
- #
- # Challenge-response is a Ruby proc that takes a single parameter and
- # returns an object that will typically be a string. The
- # challenge-response block is called when the server returns a
- # BindResponse with a result code of 14 (saslBindInProgress). The
- # challenge-response block receives a parameter containing the data
- # returned by the server in the saslServerCreds field of the LDAP
- # BindResponse packet. The challenge-response block may be called multiple
- # times during the course of a SASL authentication, and each time it must
- # return a value that will be passed back to the server as the credential
- # data in the next BindRequest packet.
- #++
- def bind_sasl(auth)
- mech, cred, chall = auth[:mechanism], auth[:initial_credential],
- auth[:challenge_response]
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall)
-
- message_id = next_msgid
-
- n = 0
- loop {
- sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
- request = [
- LdapVersion.to_ber, "".to_ber, sasl
- ].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
-
- write(request, nil, message_id)
- pdu = queued_read(message_id)
-
- if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
- raise Net::LDAP::NoBindResultError, "no bind result"
- end
-
- return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
- raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
-
- cred = chall.call(pdu.result_server_sasl_creds)
- }
-
- raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
- end
- private :bind_sasl
-
- #--
- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
- # integrate it without introducing an external dependency.
- #
- # This authentication method is accessed by calling #bind with a :method
- # parameter of :gss_spnego. It requires :username and :password
- # attributes, just like the :simple authentication method. It performs a
- # GSS-SPNEGO authentication with the server, which is presumed to be a
- # Microsoft Active Directory.
- #++
- def bind_gss_spnego(auth)
- require 'ntlm'
-
- user, psw = [auth[:username] || auth[:dn], auth[:password]]
- raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
-
- nego = proc { |challenge|
- t2_msg = NTLM::Message.parse(challenge)
- t3_msg = t2_msg.response({ :user => user, :password => psw },
- { :ntlmv2 => true })
- t3_msg.serialize
- }
-
- bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
- :initial_credential => NTLM::Message::Type1.new.serialize,
- :challenge_response => nego)
- end
- private :bind_gss_spnego
-
-
- #--
# Allow the caller to specify a sort control
#
# The format of the sort control needs to be:
#
# :sort_control => ["cn"] # just a string
@@ -650,10 +551,10 @@
write(request, nil, message_id)
pdu = queued_read(message_id)
if !pdu || pdu.app_tag != Net::LDAP::PDU::AddResponse
- raise Net::LDAP::ResponseMissingError, "response missing or invalid"
+ raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
end
pdu
end