From 82fe814c4781d3822b5c1623cf3d6474e9290949 Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 22 Sep 2023 17:51:21 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Strict=20base64=20decoding=20of?= =?UTF-8?q?=20SASL=20challenges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `unpack("m")` will silently ignore a range of bad data. By adding error handling to authenticate, we can convert those errors into cancellation of the authentication exchange rather than crashing the connection or silently ignoring them. --- lib/net/imap.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 1539dcbcf..bab17a4b4 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1253,13 +1253,12 @@ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback) if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) && SASL.initial_response?(authenticator) response = authenticator.process(nil) - cmdargs << (response.empty? ? "=" : [response].pack("m0")) + cmdargs << sasl_encode_ir(response) end result = send_command(*cmdargs) do |resp| if resp.instance_of?(ContinuationRequest) - challenge = resp.data.text.unpack1("m") - response = authenticator.process(challenge) - response = [response].pack("m0") + challenge = sasl_decode resp.data.text + response = sasl_encode authenticator.process challenge put_string(response + CRLF) end end @@ -1271,6 +1270,16 @@ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback) result end + private + + # RFC-2060 simply used base64 encoding. RFC-3051 and RFC9051 require empty + # strings be replaced with "=". + def sasl_encode_ir(str) str.empty? ? "=" : sasl_encode(str) end + def sasl_decode(str) str.unpack1("m0") end + def sasl_encode(str) [str].pack("m0") end + + public + # Sends a {LOGIN command [IMAP4rev1 ยง6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3] # to identify the client and carries the plaintext +password+ authenticating # this +user+. If successful, the connection enters the "_authenticated_"