class Net::IMAP::SASL::DigestMD5Authenticator

security. It is included for compatibility with existing servers.<br>RFC-6331 and should not be relied on for
DIGEST-MD5” has been deprecated by
== Deprecated
in RFC-2831. See Net::IMAP#authenticate.
Net::IMAP authenticator for the DIGEST-MD5 SASL mechanism type, specified

def compute_a0(response)

def compute_a0(response)
  Digest::MD5.digest(
    [ response.values_at(:username, :realm), password ].join(":")
  )
end

def compute_a1(response)

def compute_a1(response)
  a0 = compute_a0(response)
  a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":")
  a1 << ":#{response[:authzid]}" unless response[:authzid].nil?
  a1
end

def compute_a2(response)

def compute_a2(response)
  a2 = "AUTHENTICATE:#{response[:"digest-uri"]}"
  if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
    a2 << ":00000000000000000000000000000000"
  end
  a2
end

def digest_uri

would have a "digest-uri" value of "smtp/mail3.example.com/example.com".
value of "ftp/ftp.example.com"; the SMTP server from the example above
example, the FTP service on "ftp.example.com" would have a "digest-uri"
to connect, formed from the serv-type, host, and serv-name. For
Indicates the principal name of the service with which the client wishes
>>>
From RFC-2831[https://www.rfc-editor.org/rfc/rfc2831]:
def digest_uri
  if service_name && service_name != host
    "#{service}/#{host}/#{service_name}"
  else
    "#{service}/#{host}"
  end
end

def done?; @stage == STAGE_DONE end

def done?; @stage == STAGE_DONE end

def format_response(response)

def format_response(response)
  response.map {|k, v| qdval(k.to_s, v) }.join(",")
end

def initial_response?; false end

def initial_response?; false end

def initialize(user = nil, pass = nil, authz = nil,

Any other keyword arguments are silently ignored.

* _optional_ +warn_deprecation+ — Set to +false+ to silence the warning.

For Net::IMAP, this defaults to "imap".
"smtp", "ldap", "xmpp".
* _optional_ #service — the registered service protocol. E.g. "imap",
replicated.
* _optional_ #service_name — The generic host name when the server is
Defaults to #realm.
* _optional_ #host — FQDN for requested service.
Defaults to the last realm in the server-provided realms list.
* _optional_ #realm — A namespace for the #username, e.g. a domain.

identity from the authentication identity.
When +authzid+ is not set, the server should derive the authorization

* _optional_ #authzid ― Authorization identity to act as or on behalf of.

* #password ― A password or passphrase associated with this #authcid.

#username ― An alias for +authcid+.

* #authcid ― Authentication identity that is associated with #password.

==== Parameters

Called by Net::IMAP#authenticate and similar methods on other clients.

Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.

new(authcid:, password:, authzid: nil, **options) -> authenticator
new(username:, password:, authzid: nil, **options) -> authenticator
new(username, password, authzid = nil, **options) -> authenticator
:call-seq:
def initialize(user = nil, pass = nil, authz = nil,
               username: nil, password: nil, authzid: nil,
               authcid: nil, secret: nil,
               realm: nil, service: "imap", host: nil, service_name: nil,
               warn_deprecation: true, **)
  username = authcid || username || user or
    raise ArgumentError, "missing username (authcid)"
  password ||= secret || pass or raise ArgumentError, "missing password"
  authzid  ||= authz
  if warn_deprecation
    warn("WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331.",
         category: :deprecated)
  end
  require "digest/md5"
  require "securerandom"
  require "strscan"
  @username, @password, @authzid = username, password, authzid
  @realm        = realm
  @host         = host
  @service      = service
  @service_name = service_name
  @nc, @stage = {}, STAGE_ONE
end

def nc(nonce)

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

def parse_challenge(challenge)

def parse_challenge(challenge)
  sparams = Hash.new {|h, k| h[k] = [] }
  c = StringScanner.new(challenge)
  c.skip LIST_DELIM
  while c.scan AUTH_PARAM
    k, v = c[1], c[2]
    k = k.downcase
    if v =~ /\A"(.*)"\z/mn
      v = $1.gsub(/\\(.)/mn, '\1')
      v = split_quoted_list(v, challenge) if QUOTED_LISTABLE.include? k
    end
    sparams[k] << v
  end
  if !c.eos?
    raise DataFormatError, "Unparsable challenge: %p" % [challenge]
  elsif sparams.empty?
    raise DataFormatError, "Empty challenge: %p" % [challenge]
  end
  sparams
end

def process(challenge)

Responds to server challenge in two stages.
def process(challenge)
  case @stage
  when STAGE_ONE
    @stage = STAGE_TWO
    @sparams = parse_challenge(challenge)
    @qop     = sparams.key?("qop") ? ["auth"] : sparams["qop"].flatten
    @nonce   = sparams["nonce"]  &.first
    @charset = sparams["charset"]&.first
    @realm ||= sparams["realm"]  &.last
    @host  ||= realm
    if !qop.include?("auth")
      raise DataFormatError, "Server does not support auth (qop = %p)" % [
        sparams["qop"]
      ]
    elsif (emptykey = REQUIRED.find { sparams[_1].empty? })
      raise DataFormatError, "Server didn't send %s (%p)" % [emptykey, challenge]
    elsif (multikey = NO_MULTIPLES.find { sparams[_1].length > 1 })
      raise DataFormatError, "Server sent multiple %s (%p)" % [multikey, challenge]
    end
    response = {
      nonce:        nonce,
      username:     username,
      realm:        realm,
      cnonce:       SecureRandom.base64(32),
      "digest-uri": digest_uri,
      qop:          "auth",
      maxbuf:       65535,
      nc:           "%08d" % nc(nonce),
      charset:      charset,
    }
    response[:authzid] = @authzid unless @authzid.nil?
    response[:response] = response_value(response)
    format_response(response)
  when STAGE_TWO
    @stage = STAGE_DONE
    raise ResponseParseError, challenge unless challenge =~ /rspauth=/
    "" # if at the second stage, return an empty string
  else
    raise ResponseParseError, challenge
  end
rescue => error
  @stage = error
  raise
end

def qdval(k, v)

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 = v.gsub(/([\\"])/, "\\\1")
    return '%s="%s"' % [k, v]
  else
    return '%s=%s' % [k, v]
  end
end

def response_value(response)

def response_value(response)
  a1 = compute_a1(response)
  a2 = compute_a2(response)
  Digest::MD5.hexdigest(
    [
      Digest::MD5.hexdigest(a1),
      response.values_at(:nonce, :nc, :cnonce, :qop),
      Digest::MD5.hexdigest(a2)
    ].join(":")
  )
end

def split_quoted_list(value, challenge)

def split_quoted_list(value, challenge)
  value.split(LIST_DELIM).reject(&:empty?).tap do
    _1.any? or raise DataFormatError, "Bad Challenge: %p" % [challenge]
  end
end