ruby-changes:66069
From: nicholas <ko1@a...>
Date: Thu, 6 May 2021 15:20:57 +0900 (JST)
Subject: [ruby-changes:66069] 331005812f (master): [ruby/net-imap] Move each authenticator to its own file
https://git.ruby-lang.org/ruby.git/commit/?id=331005812f From 331005812fc288fb27bef542ecfbb2c061d86999 Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" <nicholas.evans@g...> Date: Tue, 27 Apr 2021 16:33:27 -0400 Subject: [ruby/net-imap] Move each authenticator to its own file Also updates rdoc with SASL specifications and deprecations. Of these four, only `PLAIN` isn't deprecated! +@@authenticators+ was changed to a class instance var +@authenticators+. No one should have been using the class variable directly, so that should be fine. https://github.com/ruby/net-imap/commit/23f241b081 --- lib/net/imap.rb | 206 +----------------------------- lib/net/imap/authenticators.rb | 44 +++++++ lib/net/imap/authenticators/cram_md5.rb | 47 +++++++ lib/net/imap/authenticators/digest_md5.rb | 111 ++++++++++++++++ lib/net/imap/authenticators/login.rb | 34 +++++ lib/net/imap/authenticators/plain.rb | 19 +++ 6 files changed, 257 insertions(+), 204 deletions(-) create mode 100644 lib/net/imap/authenticators.rb create mode 100644 lib/net/imap/authenticators/cram_md5.rb create mode 100644 lib/net/imap/authenticators/digest_md5.rb create mode 100644 lib/net/imap/authenticators/login.rb create mode 100644 lib/net/imap/authenticators/plain.rb diff --git a/lib/net/imap.rb b/lib/net/imap.rb index d3f2e25..8a6b295 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -16,8 +16,6 @@ https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L16 require "socket" require "monitor" -require "digest/md5" -require "strscan" require 'net/protocol' begin require "openssl" @@ -292,31 +290,6 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L290 @@max_flag_count = count end - # Adds an authenticator for Net::IMAP#authenticate. +auth_type+ - # 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, - # 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. - def self.add_authenticator(auth_type, authenticator) - @@authenticators[auth_type] = authenticator - end - - # Builds an authenticator for Net::IMAP#authenticate. - def self.authenticator(auth_type, *args) - auth_type = auth_type.upcase - unless @@authenticators.has_key?(auth_type) - raise ArgumentError, - format('unknown auth type - "%s"', auth_type) - end - @@authenticators[auth_type].new(*args) - end - # The default port for IMAP connections, port 143 def self.default_port return PORT @@ -1124,7 +1097,6 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1097 SSL_PORT = 993 # :nodoc: @@debug = false - @@authenticators = {} @@max_flag_count = 10000 # :call-seq: @@ -3901,182 +3873,6 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L3873 end end - # Authenticator for the "LOGIN" authentication type. See - # #authenticate(). - class LoginAuthenticator - def process(data) - case @state - when STATE_USER - @state = STATE_PASSWORD - return @user - when STATE_PASSWORD - return @password - end - end - - private - - STATE_USER = :USER - STATE_PASSWORD = :PASSWORD - - def initialize(user, password) - @user = user - @password = password - @state = STATE_USER - end - end - add_authenticator "LOGIN", LoginAuthenticator - - # Authenticator for the "PLAIN" authentication type. See - # #authenticate(). - class PlainAuthenticator - def process(data) - return "\0#{@user}\0#{@password}" - end - - private - - def initialize(user, password) - @user = user - @password = password - end - end - add_authenticator "PLAIN", PlainAuthenticator - - # Authenticator for the "CRAM-MD5" authentication type. See - # #authenticate(). - class CramMD5Authenticator - def process(challenge) - digest = hmac_md5(challenge, @password) - return @user + " " + digest - end - - private - - def initialize(user, password) - @user = user - @password = password - end - - def hmac_md5(text, key) - if key.length > 64 - key = Digest::MD5.digest(key) - end - - k_ipad = key + "\0" * (64 - key.length) - k_opad = key + "\0" * (64 - key.length) - for i in 0..63 - k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr - k_opad[i] = (k_opad[i].ord ^ 0x5c).chr - end - - digest = Digest::MD5.digest(k_ipad + text) - - return Digest::MD5.hexdigest(k_opad + digest) - end - 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 {|key| qdval(key.to_s, response[key]) }.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 responses need 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 @@ -4130,3 +3926,5 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L3926 end end end + +require_relative "imap/authenticators" diff --git a/lib/net/imap/authenticators.rb b/lib/net/imap/authenticators.rb new file mode 100644 index 0000000..f86b77b --- /dev/null +++ b/lib/net/imap/authenticators.rb @@ -0,0 +1,44 @@ https://github.com/ruby/ruby/blob/trunk/lib/net/imap/authenticators.rb#L1 +# frozen_string_literal: true + +# Registry for SASL authenticators used by Net::IMAP. +module Net::IMAP::Authenticators + + # Adds an authenticator for Net::IMAP#authenticate. +auth_type+ is the + # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # supported by +authenticator+ (for instance, "+LOGIN+"). The +authenticator+ + # is an object which defines a +#process+ method to handle authentication with + # 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. + def add_authenticator(auth_type, authenticator) + authenticators[auth_type] = authenticator + end + + # Builds an authenticator for Net::IMAP#authenticate. +args+ will be passed + # directly to the chosen authenticator's +#initialize+. + def authenticator(auth_type, *args) + auth_type = auth_type.upcase + unless authenticators.has_key?(auth_type) + raise ArgumentError, + f (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/