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