123456789_123456789_123456789_123456789_123456789_

Class: Net::IMAP::DigestMD5Authenticator

Relationships & Source Files
Inherits: Object
Defined in: lib/net/imap/authenticators/digest_md5.rb

Overview

::Net::IMAP authenticator for the “‘DIGEST-MD5`” SASL mechanism type, specified in RFC2831(tools.ietf.org/html/rfc2831). See #authenticate.

Deprecated

DIGEST-MD5” has been deprecated by RFC6331 and should not be relied on for security. It is included for compatibility with existing servers.

Constant Summary

Class Method Summary

Instance Method Summary

Constructor Details

.new(user, password, authname = nil, warn_deprecation: true) ⇒ DigestMD5Authenticator

[ GitHub ]

  
# File 'lib/net/imap/authenticators/digest_md5.rb', line 77

def initialize(user, password, authname = nil, warn_deprecation: true)
  if warn_deprecation
    warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
    # TODO: recommend SCRAM instead.
  end
  require "digest/md5"
  require "strscan"
  @user, @password, @authname = user, password, authname
  @nc, @stage = {}, STAGE_ONE
end

Instance Method Details

#nc(nonce) (private)

[ GitHub ]

  
# File 'lib/net/imap/authenticators/digest_md5.rb', line 94

def nc(nonce)
  if @nc.has_key? nonce
    @nc[nonce] = @nc[nonce] + 1
  else
    @nc[nonce] = 1
  end
  return @nc[nonce]
end

#process(challenge)

[ GitHub ]

  
# File 'lib/net/imap/authenticators/digest_md5.rb', line 12

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 Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos?
    raise Net::IMAP::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

#qdval(k, v) (private)

some responses need quoting

[ GitHub ]

  
# File 'lib/net/imap/authenticators/digest_md5.rb', line 104

def qdval(k, v)
  return if k.nil? or v.nil?
  if %w"username authzid realm nonce cnonce digest-uri qop".include? k
    v = v.gsub(/([\\"])/, "\\\1")
    return '%s="%s"' % [k, v]
  else
    return '%s=%s' % [k, v]
  end
end