class Net::IMAP::DigestMD5Authenticator

Net::IMAP authenticator for the “‘DIGEST-MD5`” SASL mechanism type, specified in RFC2831(tools.ietf.org/html/rfc2831). See Net::IMAP#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.

Constants

STAGE_ONE
STAGE_TWO

Public Class Methods

new(user, password, authname = nil, warn_deprecation: true) click to toggle source
# File net-imap-0.3.7/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

Public Instance Methods

process(challenge) click to toggle source
# File net-imap-0.3.7/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? and sparams['qop']
    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

Private Instance Methods

nc(nonce) click to toggle source
# File net-imap-0.3.7/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
qdval(k, v) click to toggle source

some responses need quoting

# File net-imap-0.3.7/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