lib/xmpp4r/sasl.rb in xmpp4r-0.3 vs lib/xmpp4r/sasl.rb in xmpp4r-0.3.1

- old
+ new

@@ -1,5 +1,9 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + require 'base64' require 'digest/md5' module Jabber ## @@ -39,11 +43,11 @@ auth.text = text auth end def generate_nonce - Digest::MD5.new(Time.new.to_f.to_s).hexdigest + Digest::MD5.hexdigest(Time.new.to_f.to_s) end end ## # SASL PLAIN authentication helper (RFC2595) @@ -76,17 +80,11 @@ challenge = {} error = nil @stream.send(generate_auth('DIGEST-MD5')) { |reply| if reply.name == 'challenge' and reply.namespace == NS_SASL - challenge_text = Base64::decode64(reply.text) - challenge_text.split(/,/).each { |s| - key, value = s.split(/=/, 2) - value.sub!(/^"/, '') - value.sub!(/"$/, '') - challenge[key] = value - } + challenge = decode_challenge(reply.text) else error = reply.first_element(nil).name end true } @@ -94,10 +92,53 @@ @nonce = challenge['nonce'] @realm = challenge['realm'] end + def decode_challenge(challenge) + text = Base64::decode64(challenge) + res = {} + + state = :key + key = '' + value = '' + + text.scan(/./) do |ch| + if state == :key + if ch == '=' + state = :value + else + key += ch + end + + elsif state == :value + if ch == ',' + res[key] = value + key = '' + value = '' + state = :key + elsif ch == '"' and value == '' + state = :quote + else + value += ch + end + + elsif state == :quote + if ch == '"' + state = :value + else + value += ch + end + end + end + res[key] = value unless key == '' + + Jabber::debuglog("SASL DIGEST-MD5 challenge:\n#{text.inspect}\n#{res.inspect}") + + res + end + ## # * Send a response # * Wait for the server's challenge (which aren't checked) # * Send a blind response to the server's challenge def auth(password) @@ -115,20 +156,28 @@ unless %w(nc qop response charset).include? key response[key] = "\"#{value}\"" end } + response_text = response.collect { |k,v| "#{k}=#{v}" }.join(',') + Jabber::debuglog("SASL DIGEST-MD5 response:\n#{response_text}") + r = REXML::Element.new('response') r.add_namespace NS_SASL - r.text = Base64::encode64(response.collect { |k,v| "#{k}=#{v}" }.join(',')).gsub(/\s/, '') + r.text = Base64::encode64(response_text).gsub(/\s/, '') + + success_already = false error = nil @stream.send(r) { |reply| - if reply.name != 'challenge' + if reply.name == 'success' + success_already = true + elsif reply.name != 'challenge' error = reply.first_element(nil).name end true } + return if success_already raise error if error # TODO: check the challenge from the server r.text = nil