aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorshugo <shugo@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-09-16 23:05:13 +0000
committershugo <shugo@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-09-16 23:05:13 +0000
commitaa6393778a0255ce0fe3dbe9403218d20ad3cd5c (patch)
treebef965ca77c6cf748bfb7f64f26f10ad4ac990d2
parent5a2f770fb1d235993fcc3d951ab4c027e7eba84f (diff)
downloadruby-aa6393778a0255ce0fe3dbe9403218d20ad3cd5c.tar.gz
* lib/net/imap.rb: supported DIGEST-MD5. Thanks, Mathieu Arnold.
* lib/net/imap.rb: use fcall instead of send. Thanks, Satoru Takabayashi. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@9188 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog7
-rw-r--r--lib/net/imap.rb117
2 files changed, 117 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index bcc92277b0..6ccc9c1fa3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Sat Sep 17 08:02:53 2005 Shugo Maeda <shugo@ruby-lang.org>
+
+ * lib/net/imap.rb: supported DIGEST-MD5. Thanks, Mathieu Arnold.
+
+ * lib/net/imap.rb: use fcall instead of send. Thanks, Satoru
+ Takabayashi.
+
Fri Sep 16 22:45:49 2005 Nobuyoshi Nakada <nobu@ruby-lang.org>
* file.c (rb_file_s_extname): empty string for path name ending with a
diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index a5c1433714..b4ea72b87c 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -16,6 +16,7 @@
require "socket"
require "monitor"
require "digest/md5"
+require "strscan"
begin
require "openssl"
rescue LoadError
@@ -273,8 +274,10 @@ module Net
# is the type of authentication this authenticator supports
# (for instance, "LOGIN"). The +authenticator+ is an object
# which defines a process() method to handle authentication with
- # the server. See Net::IMAP::LoginAuthenticator and
- # Net::IMAP::CramMD5Authenticator for examples.
+ # the server. See Net::IMAP::LoginAuthenticator,
+ # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
+ # for examples.
+ #
#
# If +auth_type+ refers to an existing authenticator, it will be
# replaced by the new one.
@@ -1224,7 +1227,7 @@ module Net
class RawData # :nodoc:
def send_data(imap)
- imap.send(:put_string, @data)
+ imap.fcall(:put_string, @data)
end
private
@@ -1236,7 +1239,7 @@ module Net
class Atom # :nodoc:
def send_data(imap)
- imap.send(:put_string, @data)
+ imap.fcall(:put_string, @data)
end
private
@@ -1248,7 +1251,7 @@ module Net
class QuotedString # :nodoc:
def send_data(imap)
- imap.send(:send_quoted_string, @data)
+ imap.fcall(:send_quoted_string, @data)
end
private
@@ -1260,7 +1263,7 @@ module Net
class Literal # :nodoc:
def send_data(imap)
- imap.send(:send_literal, @data)
+ imap.fcall(:send_literal, @data)
end
private
@@ -1272,7 +1275,7 @@ module Net
class MessageSet # :nodoc:
def send_data(imap)
- imap.send(:put_string, format_internal(@data))
+ imap.fcall(:put_string, format_internal(@data))
end
private
@@ -3080,6 +3083,106 @@ module Net
end
add_authenticator "CRAM-MD5", CramMD5Authenticator
+ # Authenticator for the "DIGEST-MD5" authentication type. See
+ # #authenticate().
+ class DigestMD5Authenticator
+ def process(challenge)
+ case @stage
+ when STAGE_ONE
+ @stage = STAGE_TWO
+ sparams = {}
+ c = StringScanner.new(challenge)
+ while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
+ k, v = c[1], c[2]
+ if v =~ /^"(.*)"$/
+ v = $1
+ if v =~ /,/
+ v = v.split(',')
+ end
+ end
+ sparams[k] = v
+ end
+
+ raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
+ raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
+
+ response = {
+ :nonce => sparams['nonce'],
+ :username => @user,
+ :realm => sparams['realm'],
+ :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
+ :'digest-uri' => 'imap/' + sparams['realm'],
+ :qop => 'auth',
+ :maxbuf => 65535,
+ :nc => "%08d" % nc(sparams['nonce']),
+ :charset => sparams['charset'],
+ }
+
+ response[:authzid] = @authname unless @authname.nil?
+
+ # now, the real thing
+ a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
+
+ a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
+ a1 << ':' + response[:authzid] unless response[:authzid].nil?
+
+ a2 = "AUTHENTICATE:" + response[:'digest-uri']
+ a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
+
+ response[:response] = Digest::MD5.hexdigest(
+ [
+ Digest::MD5.hexdigest(a1),
+ response.values_at(:nonce, :nc, :cnonce, :qop),
+ Digest::MD5.hexdigest(a2)
+ ].join(':')
+ )
+
+ return response.keys.map { |k| qdval(k.to_s, response[k]) }.join(',')
+ when STAGE_TWO
+ @stage = nil
+ # if at the second stage, return an empty string
+ if challenge =~ /rspauth=/
+ return ''
+ else
+ raise ResponseParseError, challenge
+ end
+ else
+ raise ResponseParseError, challenge
+ end
+ end
+
+ def initialize(user, password, authname = nil)
+ @user, @password, @authname = user, password, authname
+ @nc, @stage = {}, STAGE_ONE
+ end
+
+ private
+
+ STAGE_ONE = :stage_one
+ STAGE_TWO = :stage_two
+
+ def nc(nonce)
+ if @nc.has_key? nonce
+ @nc[nonce] = @nc[nonce] + 1
+ else
+ @nc[nonce] = 1
+ end
+ return @nc[nonce]
+ end
+
+ # some reponses needs quoting
+ def qdval(k, v)
+ return if k.nil? or v.nil?
+ if %w"username authzid realm nonce cnonce digest-uri qop".include? k
+ v.gsub!(/([\\"])/, "\\\1")
+ return '%s="%s"' % [k, v]
+ else
+ return '%s=%s' % [k, v]
+ end
+ end
+ end
+ add_authenticator "DIGEST-MD5", DigestMD5Authenticator
+
# Superclass of IMAP errors.
class Error < StandardError
end