@@ -13,6 +13,16 @@ class LDAP
1313 HasOpenSSL = false
1414 # :startdoc:
1515 end
16+ begin
17+ require 'sasl'
18+ ##
19+ # Set to +true+ if SASL is available
20+ HasSASL = true
21+ rescue LoadError
22+ # :stopdoc:
23+ HasSASL = false
24+ # :startdoc:
25+ end
1626 end
1727end
1828require 'socket'
@@ -576,7 +586,8 @@ def open
576586 :port => @port ,
577587 :encryption =>
578588 @encryption )
579- @open_connection . bind ( @auth )
589+
590+ conn_bind ( @open_connection , @auth )
580591 yield self
581592 ensure
582593 @open_connection . close if @open_connection
@@ -650,7 +661,7 @@ def search(args = {})
650661 begin
651662 conn = Net ::LDAP ::Connection . new ( :host => @host , :port => @port ,
652663 :encryption => @encryption )
653- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
664+ if ( @result = conn_bind ( conn , args [ :auth ] || @auth ) ) . result_code == 0
654665 @result = conn . search ( args ) { |entry |
655666 result_set << entry if result_set
656667 yield entry if block_given?
@@ -727,12 +738,12 @@ def search(args = {})
727738 # instead of a String.
728739 def bind ( auth = @auth )
729740 if @open_connection
730- @result = @open_connection . bind ( auth )
741+ @result = conn_bind ( @open_connection , auth )
731742 else
732743 begin
733744 conn = Connection . new ( :host => @host , :port => @port ,
734745 :encryption => @encryption )
735- @result = conn . bind ( auth )
746+ @result = conn_bind ( conn , auth )
736747 ensure
737748 conn . close if conn
738749 end
@@ -741,6 +752,13 @@ def bind(auth = @auth)
741752 @result . success?
742753 end
743754
755+ def conn_bind ( conn , auth )
756+ auth = auth . dup
757+ auth [ :host ] = @host unless auth . include? ( :host )
758+ conn . bind ( auth )
759+ end
760+ private :conn_bind
761+
744762 # #bind_as is for testing authentication credentials.
745763 #
746764 # As described under #bind, most LDAP servers require that you supply a
@@ -833,7 +851,7 @@ def add(args)
833851 begin
834852 conn = Connection . new ( :host => @host , :port => @port ,
835853 :encryption => @encryption )
836- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
854+ if ( @result = conn_bind ( conn , args [ :auth ] || @auth ) ) . result_code == 0
837855 @result = conn . add ( args )
838856 end
839857 ensure
@@ -931,7 +949,7 @@ def modify(args)
931949 begin
932950 conn = Connection . new ( :host => @host , :port => @port ,
933951 :encryption => @encryption )
934- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
952+ if ( @result = conn_bind ( conn , args [ :auth ] || @auth ) ) . result_code == 0
935953 @result = conn . modify ( args )
936954 end
937955 ensure
@@ -1003,7 +1021,7 @@ def rename(args)
10031021 begin
10041022 conn = Connection . new ( :host => @host , :port => @port ,
10051023 :encryption => @encryption )
1006- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
1024+ if ( @result = conn_bind ( conn , args [ :auth ] || @auth ) ) . result_code == 0
10071025 @result = conn . rename ( args )
10081026 end
10091027 ensure
@@ -1031,7 +1049,7 @@ def delete(args)
10311049 begin
10321050 conn = Connection . new ( :host => @host , :port => @port ,
10331051 :encryption => @encryption )
1034- if ( @result = conn . bind ( args [ :auth ] || @auth ) ) . result_code == 0
1052+ if ( @result = conn_bind ( conn , args [ :auth ] || @auth ) ) . result_code == 0
10351053 @result = conn . delete ( args )
10361054 end
10371055 ensure
@@ -1172,6 +1190,14 @@ def self.wrap_with_ssl(io)
11721190 conn
11731191 end
11741192
1193+ def self . wrap_with_sasl ( io , securelayer_wrapper )
1194+ raise Net ::LDAP ::LdapError , "SASL library (gem ruby-sasl) is unavailable" unless Net ::LDAP ::HasSASL
1195+ conn = securelayer_wrapper . call ( io )
1196+ conn . extend ( Net ::BER ::BERParser )
1197+ conn . extend ( SASL ::Buffering )
1198+ conn . extend ( GetbyteForSSLSocket ) unless conn . respond_to? ( :getbyte )
1199+ end
1200+
11751201 #--
11761202 # Helper method called only from new, and only after we have a
11771203 # successfully-opened @conn instance variable, which is a TCP connection.
@@ -1276,14 +1302,22 @@ def bind_simple(auth)
12761302 end
12771303
12781304 #--
1279- # Required parameters: :mechanism, :initial_credential and
1280- # :challenge_response
1305+ # Required parameters: :mechanism
12811306 #
1282- # Mechanism is a string value that will be passed in the SASL-packet's
1283- # "mechanism" field.
1307+ # If ruby-sasl is avaiable, :host is used to setup digest-url and defaults to
1308+ # connection hostname. Also, for some mechs, the :username and :password are used.
1309+ # :initial_credential and :challenge_response are optional and filled by
1310+ # setup_auth_sasl.
1311+ #
1312+ # If ruby-sasl is not avaiable, :initial_credential and :challenge_response
1313+ # are required.
12841314 #
12851315 # Initial credential is most likely a string. It's passed in the initial
1286- # BindRequest that goes to the server. In some protocols, it may be empty.
1316+ # BindRequest that goes to the server. In some protocols, it may be empty
1317+ # or missing.
1318+ #
1319+ # Mechanism is a string value that will be passed in the SASL-packet's
1320+ # "mechanism" field.
12871321 #
12881322 # Challenge-response is a Ruby proc that takes a single parameter and
12891323 # returns an object that will typically be a string. The
@@ -1295,13 +1329,18 @@ def bind_simple(auth)
12951329 # times during the course of a SASL authentication, and each time it must
12961330 # return a value that will be passed back to the server as the credential
12971331 # data in the next BindRequest packet.
1332+ #
12981333 #++
12991334 def bind_sasl ( auth )
1335+ if not auth [ :challenge_response ]
1336+ auth = auth . merge ( self . class . setup_auth_sasl ( auth ) )
1337+ end
13001338 mech , cred , chall = auth [ :mechanism ] , auth [ :initial_credential ] ,
13011339 auth [ :challenge_response ]
13021340 raise Net ::LDAP ::LdapError , "Invalid binding information" unless ( mech && chall )
13031341
13041342 n = 0
1343+ securelayer_wrapper = nil
13051344 loop {
13061345 msgid = next_msgid . to_ber
13071346 if cred
@@ -1314,46 +1353,41 @@ def bind_sasl(auth)
13141353 @conn . write request_pkt
13151354
13161355 ( be = @conn . read_ber ( Net ::LDAP ::AsnSyntax ) and pdu = Net ::LDAP ::PDU . new ( be ) ) or raise Net ::LDAP ::LdapError , "no bind result"
1356+ if securelayer_wrapper
1357+ $stderr. puts "secure lay"
1358+ @conn = self . class . wrap_with_sasl ( @conn , securelayer_wrapper )
1359+ end
13171360 return pdu unless pdu . result_code == 14 # saslBindInProgress
13181361 raise Net ::LDAP ::LdapError , "sasl-challenge overflow" if ( ( n += 1 ) > MaxSaslChallenges )
13191362
1320- cred = chall . call ( pdu . result_server_sasl_creds )
1363+ ( cred , securelayer_wrapper ) = chall . call ( pdu . result_server_sasl_creds )
13211364 }
13221365
13231366 raise Net ::LDAP ::LdapError , "why are we here?"
13241367 end
13251368 private :bind_sasl
13261369
1327- #--
1328- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
1329- # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
1330- # integrate it without introducing an external dependency.
1331- #
1332- # This authentication method is accessed by calling #bind with a :method
1333- # parameter of :gss_spnego. It requires :username and :password
1334- # attributes, just like the :simple authentication method. It performs a
1335- # GSS-SPNEGO authentication with the server, which is presumed to be a
1336- # Microsoft Active Directory.
1337- #++
1338- def bind_gss_spnego ( auth )
1339- require 'ntlm'
1370+ SASL_SERVICE_NAME = "ldap"
13401371
1341- user , psw = [ auth [ :username ] || auth [ :dn ] , auth [ :password ] ]
1342- raise Net ::LDAP ::LdapError , "Invalid binding information" unless ( user && psw )
1372+ def self . setup_auth_sasl ( auth )
1373+ raise Net ::LDAP ::LdapError , "SASL library (gem ruby-sasl) is unavailable" unless Net ::LDAP ::HasSASL
1374+ raise Net ::LDAP ::LdapError , "Invalid binding information. auth[:host] is required" unless auth . include? ( :host )
13431375
1344- nego = proc { | challenge |
1345- t2_msg = NTLM :: Message . parse ( challenge )
1346- t3_msg = t2_msg . response ( { :user => user , :password => psw } ,
1347- { :ntlmv2 => true } )
1348- t3_msg . serialize
1349- }
1376+ pref = SASL :: Preferences . new (
1377+ :digest_uri => " #{ SASL_SERVICE_NAME } / #{ auth [ :host ] } " ,
1378+ :username => auth [ :username ] || auth [ :dn ] ,
1379+ :password => auth [ :password ] ,
1380+ )
1381+ sasl = SASL . new_mechanism ( auth [ :mechanism ] , pref )
13501382
1351- bind_sasl ( :method => :sasl , :mechanism => "GSS-SPNEGO" ,
1352- :initial_credential => NTLM ::Message ::Type1 . new . serialize ,
1353- :challenge_response => nego )
1354- end
1355- private :bind_gss_spnego
1383+ challenge_response = Proc . new do |cred |
1384+ response = sasl . receive ( "challenge" , cred )
1385+ response [ 1 ]
1386+ end
13561387
1388+ initial_credential = sasl . start [ 1 ]
1389+ return { :initial_credential => initial_credential , :challenge_response => challenge_response }
1390+ end
13571391
13581392 #--
13591393 # Allow the caller to specify a sort control
0 commit comments