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) ⇒ DigestMD5Authenticator

[ GitHub ]

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

def initialize(user, password, authname = nil)
  @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 90

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 15

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

#qdval(k, v) (private)

some responses need quoting

[ GitHub ]

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

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