Skip to content

Commit

Permalink
🔒 Add SASL ANONYMOUS mechanism
Browse files Browse the repository at this point in the history
See https://tools.ietf.org/html/rfc4505 for the specification.
  • Loading branch information
nevans committed Sep 16, 2023
1 parent 43a73ec commit 3ad2ad8
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,12 @@ def starttls(options = {}, verify = true)
# Each mechanism has different properties and requirements. Please consult
# the documentation for the specific mechanisms you are using:
#
# +ANONYMOUS+::
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
#
# Allows the user to gain access to public services or resources without
# authenticating or disclosing an identity.
#
# +OAUTHBEARER+::
# See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
#
Expand Down
8 changes: 8 additions & 0 deletions lib/net/imap/sasl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class IMAP
# Each mechanism has different properties and requirements. Please consult
# the documentation for the specific mechanisms you are using:
#
# +ANONYMOUS+::
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
#
# Allows the user to gain access to public services or resources without
# authenticating or disclosing an identity.
#
# +OAUTHBEARER+::
# See OAuthBearerAuthenticator.
#
Expand Down Expand Up @@ -77,6 +83,8 @@ module SASL
sasl_dir = File.expand_path("sasl", __dir__)
autoload :Authenticators, "#{sasl_dir}/authenticators"
autoload :GS2Header, "#{sasl_dir}/gs2_header"

autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator"
autoload :OAuthBearerAuthenticator, "#{sasl_dir}/oauthbearer_authenticator"
autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator"
autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator"
Expand Down
56 changes: 56 additions & 0 deletions lib/net/imap/sasl/anonymous_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module Net
class IMAP < Protocol
module SASL

# Authenticator for the "+ANONYMOUS+" SASL mechanism, as specified by
# RFC-4505[https://tools.ietf.org/html/rfc4505]. See
# Net::IMAP#authenticate.
class AnonymousAuthenticator

# :call-seq:
# new(anonymous_message = "", **) -> authenticator
# new(anonymous_message: "", **) -> authenticator
#
# Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as
# specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use
# this, see Net::IMAP#authenticate or your client's authentication
# method.
#
# #anonymous_message is an optional message which is sent to the server.
# It may be sent as a positional argument or as a keyword argument.
#
# Any other keyword parameters are quietly ignored.
def initialize(anon_msg = nil, anonymous_message: nil, **)
message = (anonymous_message || anon_msg || "").to_str
@anonymous_message = StringPrep::Trace.stringprep_trace message
if (size = @anonymous_message&.length)&.> 255
raise ArgumentError,
"anonymous_message is too long. (%d codepoints)" % [size]
end
end

# A token sent for the +ANONYMOUS+ mechanism.
#
# If it contains an "@" sign, the message must be a valid email address
# (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
# Email syntax is _not_ validated by AnonymousAuthenticator.
#
# Otherwise, it can be any UTF8 string which is permitted by the
# StringPrep::Trace profile, up to 255 UTF-8 characters in length.
attr_reader :anonymous_message

# :call-seq:
# initial_response? -> true
#
# +ANONYMOUS+ can send an initial client response.
def initial_response?; true end

# Returns #anonymous_message.
def process(_server_challenge_string) anonymous_message end

end
end
end
end
1 change: 1 addition & 0 deletions lib/net/imap/sasl/authenticators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Authenticators
def initialize(use_defaults: false)
@authenticators = {}
if use_defaults
add_authenticator "Anonymous"
add_authenticator "OAuthBearer"
add_authenticator "Plain"
add_authenticator "XOAuth2"
Expand Down
35 changes: 35 additions & 0 deletions test/net/imap/test_imap_authenticators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,41 @@ def test_xoauth2_supports_initial_response
assert Net::IMAP::SASL.initial_response?(xoauth2("foo", "bar"))
end

# ----------------------
# ANONYMOUS
# ----------------------

def anonymous(*args, **kwargs, &block)
Net::IMAP::SASL.authenticator("ANONYMOUS", *args, **kwargs, &block)
end

def test_anonymous_matches_mechanism
assert_kind_of(Net::IMAP::SASL::AnonymousAuthenticator, anonymous)
end

def test_anonymous_response
assert_equal("", anonymous.process(nil))
assert_equal("hello world", anonymous("hello world").process(nil))
assert_equal("kwargs",
anonymous(anonymous_message: "kwargs").process(nil))
end

def test_anonymous_stringprep
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
anonymous("no\ncontrol\rchars").process(nil)
}
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
anonymous("regional flags use tagging chars: e.g." \
"🏴󠁧󠁢󠁥󠁮󠁧󠁿 England, " \
"🏴󠁧󠁢󠁳󠁣󠁴󠁿 Scotland, " \
"🏴󠁧󠁢󠁷󠁬󠁳󠁿 Wales.").process(nil)
}
end

def test_anonymous_length_over_255
assert_raise(ArgumentError) { anonymous("a" * 256).process(nil) }
end

# ----------------------
# LOGIN (obsolete)
# ----------------------
Expand Down

0 comments on commit 3ad2ad8

Please sign in to comment.