Skip to content

Commit 212ef7a

Browse files
committed
♻️💩 SASL DIGEST-MD5 detritus
These are further changes I made while working on the SASL authenticators that never got merged... However... it's still deprecated, so don't use it! 🙃
1 parent ea47e34 commit 212ef7a

File tree

1 file changed

+68
-44
lines changed

1 file changed

+68
-44
lines changed

lib/net/imap/sasl/digest_md5_authenticator.rb

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,6 @@ def initialize(user = nil, pass = nil, authz = nil,
156156
authcid: nil, secret: nil,
157157
realm: nil, service: "imap", host: nil, service_name: nil,
158158
warn_deprecation: true, **)
159-
username = authcid || username || user or
160-
raise ArgumentError, "missing username (authcid)"
161-
password ||= secret || pass or raise ArgumentError, "missing password"
162-
authzid ||= authz
163159
if warn_deprecation
164160
warn("WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331.",
165161
category: :deprecated)
@@ -168,11 +164,25 @@ def initialize(user = nil, pass = nil, authz = nil,
168164
require "digest/md5"
169165
require "securerandom"
170166
require "strscan"
171-
@username, @password, @authzid = username, password, authzid
167+
168+
[authcid, username, user].compact.count == 1 or
169+
raise ArgumentError, "conflicting values for username"
170+
[authzid, authz].compact.count <= 1 or
171+
raise ArgumentError, "conflicting values for authzid"
172+
[password, secret, pass].compact.count == 1 or
173+
raise ArgumentError, "conflicting values for password"
174+
175+
@username = authcid || username || user
176+
@password = password || secret || pass
177+
@authzid = authzid || authz
172178
@realm = realm
173179
@host = host
174180
@service = service
175181
@service_name = service_name
182+
183+
@username or raise ArgumentError, "missing username (authcid)"
184+
@password or raise ArgumentError, "missing password"
185+
176186
@nc, @stage = {}, STAGE_ONE
177187
end
178188

@@ -198,42 +208,11 @@ def process(challenge)
198208
case @stage
199209
when STAGE_ONE
200210
@stage = STAGE_TWO
201-
@sparams = parse_challenge(challenge)
202-
@qop = sparams.key?("qop") ? ["auth"] : sparams["qop"].flatten
203-
@nonce = sparams["nonce"] &.first
204-
@charset = sparams["charset"]&.first
205-
@realm ||= sparams["realm"] &.last
206-
@host ||= realm
207-
208-
if !qop.include?("auth")
209-
raise DataFormatError, "Server does not support auth (qop = %p)" % [
210-
sparams["qop"]
211-
]
212-
elsif (emptykey = REQUIRED.find { sparams[_1].empty? })
213-
raise DataFormatError, "Server didn't send %s (%p)" % [emptykey, challenge]
214-
elsif (multikey = NO_MULTIPLES.find { sparams[_1].length > 1 })
215-
raise DataFormatError, "Server sent multiple %s (%p)" % [multikey, challenge]
216-
end
217-
218-
response = {
219-
nonce: nonce,
220-
username: username,
221-
realm: realm,
222-
cnonce: SecureRandom.base64(32),
223-
"digest-uri": digest_uri,
224-
qop: "auth",
225-
maxbuf: 65535,
226-
nc: "%08d" % nc(nonce),
227-
charset: charset,
228-
}
229-
230-
response[:authzid] = @authzid unless @authzid.nil?
231-
232-
response[:response] = response_value(response)
233-
format_response(response)
211+
process_stage_one(challenge)
212+
stage_one_response
234213
when STAGE_TWO
235214
@stage = STAGE_DONE
236-
raise ResponseParseError, challenge unless challenge =~ /rspauth=/
215+
process_stage_two(challenge)
237216
"" # if at the second stage, return an empty string
238217
else
239218
raise ResponseParseError, challenge
@@ -284,14 +263,59 @@ def split_quoted_list(value, challenge)
284263
end
285264

286265
def nc(nonce)
287-
if @nc.has_key? nonce
288-
@nc[nonce] = @nc[nonce] + 1
289-
else
290-
@nc[nonce] = 1
266+
@nc[nonce] = @nc.key?(nonce) ? @nc[nonce] + 1 : 1
267+
@nc[nonce]
268+
end
269+
270+
def process_stage_one(challenge)
271+
@sparams = parse_challenge(challenge)
272+
@qop = sparams.key?("qop") ? ["auth"] : sparams["qop"].flatten
273+
274+
guard_stage_one(challenge)
275+
276+
@nonce = sparams["nonce"] .first
277+
@charset = sparams["charset"].first
278+
279+
@realm ||= sparams["realm"].last
280+
@host ||= realm
281+
end
282+
283+
def guard_stage_one(challenge)
284+
if !qop.include?("auth")
285+
raise DataFormatError, "Server does not support auth (qop = %p)" % [
286+
sparams["qop"]
287+
]
288+
elsif (emptykey = REQUIRED.find { sparams[_1].empty? })
289+
raise DataFormatError, "Server didn't send %s (%p)" % [emptykey, challenge]
290+
elsif (multikey = NO_MULTIPLES.find { sparams[_1].length > 1 })
291+
raise DataFormatError, "Server sent multiple %s (%p)" % [multikey, challenge]
291292
end
292293
end
293294

294-
def response_value(response)
295+
def stage_one_response
296+
response = {
297+
nonce: nonce,
298+
username: username,
299+
realm: realm,
300+
cnonce: SecureRandom.base64(32),
301+
"digest-uri": digest_uri,
302+
qop: "auth",
303+
maxbuf: 65535,
304+
nc: "%08d" % nc(nonce),
305+
charset: charset,
306+
}
307+
308+
response[:authzid] = authzid unless authzid.nil?
309+
response[:response] = compute_digest(response)
310+
311+
format_response(response)
312+
end
313+
314+
def process_stage_two(challenge)
315+
raise ResponseParseError, challenge unless challenge =~ /rspauth=/
316+
end
317+
318+
def compute_digest(response)
295319
a1 = compute_a1(response)
296320
a2 = compute_a2(response)
297321
Digest::MD5.hexdigest(

0 commit comments

Comments
 (0)