Skip to content

Commit 325908e

Browse files
committed
✨ SASL ANONYMOUS: Add new mechanism
1 parent 944b55b commit 325908e

File tree

5 files changed

+170
-1
lines changed

5 files changed

+170
-1
lines changed

lib/net/imap.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,9 @@ def starttls(options = {}, verify = true)
809809
#
810810
# PLAIN:: Login using clear-text user and password. Secure with TLS.
811811
# See SASL::PlainAuthenticator.
812+
# ANONYMOUS:: Allow the user to gain access to public services or resources
813+
# without authenticating or disclosing identity to the server.
814+
# See SASL::AnonymousAuthenticator.
812815
# XOAUTH2:: Login using a username and OAuth2 access token. Non-standard
813816
# and obsoleted by +OAUTHBEARER+, but still widely supported.
814817
# See SASL::XOAuth2Authenticator.

lib/net/imap/authenticators.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def authenticators
9999

100100
require_relative "sasl/plain_authenticator"
101101
require_relative "sasl/xoauth2_authenticator"
102+
require_relative "sasl/anonymous_authenticator"
102103

103104
# deprecated
104105
require_relative "authenticators/login"
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "authenticator"
4+
5+
module Net
6+
class IMAP < Protocol
7+
module SASL
8+
9+
# Authenticator for the "+ANONYMOUS+" SASL mechanism, as specified by
10+
# RFC-4505[https://tools.ietf.org/html/rfc4505]. See
11+
# Net::IMAP#authenticate.
12+
class AnonymousAuthenticator < Authenticator
13+
register Net::IMAP
14+
15+
# :call-seq:
16+
# initial_response? -> true
17+
#
18+
# +ANONYMOUS+ can send an initial client response.
19+
def initial_response?; true end
20+
21+
##
22+
# :call-seq:
23+
# new -> authenticator
24+
# new(anonymous_message, **) -> authenticator
25+
# new(anonymous_message:, **) -> authenticator
26+
# new(message:, **) -> authenticator
27+
# new {|propname, auth_ctx| propval } -> authenticator
28+
#
29+
# Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as
30+
# specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use
31+
# this, see Net::IMAP#authenticate or your client's authentication
32+
# method.
33+
#
34+
# ==== Properties
35+
# Only one optional property:
36+
#
37+
# * #anonymous_message --- an optional message sent to the server which
38+
# doesn't contain an <tt>"@"</tt> character, or if it does have an
39+
# <tt>"@"</tt> it must be a valid email address.
40+
#
41+
# May be sent as positional argument or as a keyword argument.
42+
# Aliased as #message.
43+
#
44+
# See Net::IMAP::SASL::Authenticator@Properties for a detailed
45+
# description of property assignment, lazy loading, and callbacks.
46+
def initialize(message_arg = nil, anonymous_message: nil, message: nil)
47+
super
48+
propinit :anonymous_message, message_arg, anonymous_message, message
49+
end
50+
51+
##
52+
# method: anonymous_message
53+
# :call-seq:
54+
# anonymous_message -> string or nil
55+
#
56+
# A token sent for the +ANONYMOUS+ mechanism.
57+
#
58+
# Restricted to 255 UTF8 encoded characters, which will be validated by
59+
# #process.
60+
#
61+
# If an "@" sign is included, the message must be a valid email address
62+
# (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
63+
# Email syntax will _not_ be validated by AnonymousAuthenticator.
64+
#
65+
# Otherwise, it can be any UTF8 string which is permitted by the
66+
# StringPrep "+trace+" profile. This is validated by #process.
67+
# See AnonymousAuthenticator.stringprep_trace.
68+
property :anonymous_message
69+
alias message anonymous_message
70+
71+
# From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
72+
# Profile of "Stringprep":
73+
# >>>
74+
# Characters from the following tables of [StringPrep] are prohibited:
75+
#
76+
# - C.2.1 (ASCII control characters)
77+
# - C.2.2 (Non-ASCII control characters)
78+
# - C.3 (Private use characters)
79+
# - C.4 (Non-character code points)
80+
# - C.5 (Surrogate codes)
81+
# - C.6 (Inappropriate for plain text)
82+
# - C.8 (Change display properties are deprecated)
83+
# - C.9 (Tagging characters)
84+
#
85+
# No additional characters are prohibited.
86+
SASLPREP_TRACE_TABLES = %w[C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.8 C.9].freeze
87+
88+
# From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
89+
# Profile of "Stringprep":
90+
# >>>
91+
# The character repertoire of this profile is Unicode 3.2 [Unicode].
92+
#
93+
# No mapping is required by this profile.
94+
#
95+
# No Unicode normalization is required by this profile.
96+
#
97+
# The list of unassigned code points for this profile is that provided
98+
# in Appendix A of [StringPrep]. Unassigned code points are not
99+
# prohibited.
100+
#
101+
# Characters from the following tables of [StringPrep] are prohibited:
102+
# (documented on SASLPREP_TRACE_TABLES)
103+
#
104+
# This profile requires bidirectional character checking per Section 6
105+
# of [StringPrep].
106+
def self.stringprep_trace(string)
107+
StringPrep.check_prohibited!(string,
108+
*SASLPREP_TRACE_TABLES,
109+
bidi: true,
110+
profile: "trace")
111+
string
112+
end
113+
114+
# Returns the #anonymous_message, after checking it with
115+
# rdoc-ref:AnonymousAuthenticator.stringprep_trace.
116+
def process(_server_challenge_string)
117+
if (size = anonymous_message&.length)&.> 255
118+
raise Error, "anonymous_message is too long. (%d codepoints)" % [
119+
size
120+
]
121+
end
122+
self.class.stringprep_trace(anonymous_message || "")
123+
end
124+
125+
end
126+
end
127+
end
128+
end

lib/net/imap/sasl/authenticator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module SASL
1818
# * +PLAIN+ --- PlainAuthenticator
1919
# * +XOAUTH2+ --- XOAuth2Authenticator
2020
# * +EXTERNAL+ --- TODO
21-
# * +ANONYMOUS+ --- TODO
21+
# * +ANONYMOUS+ --- AnonymousAuthenticator
2222
# * +OAUTHBEARER+ --- TODO
2323
# * +SCRAM-SHA-*+ --- TODO
2424
#

test/net/imap/sasl/test_authenticators.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,43 @@ def test_xoauth2_callbacks
8080
)
8181
end
8282

83+
# ----------------------
84+
# ANONYMOUS
85+
# ----------------------
86+
87+
def anonymous(*args, **kwargs, &block)
88+
Net::IMAP.authenticator("ANONYMOUS", *args, **kwargs, &block)
89+
end
90+
91+
def test_anonymous_matches_mechanism
92+
assert_kind_of(Net::IMAP::SASL::AnonymousAuthenticator, anonymous)
93+
end
94+
95+
def test_anonymous_response
96+
assert_equal("", anonymous.process(nil))
97+
assert_equal("hello world", anonymous("hello world").process(nil))
98+
assert_equal("kwargs",
99+
anonymous(anonymous_message: "kwargs").process(nil))
100+
end
101+
102+
def test_anonymous_stringprep
103+
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
104+
anonymous("no\ncontrol\rchars").process(nil)
105+
}
106+
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
107+
anonymous("regional flags use tagging chars: e.g." \
108+
"🏴󠁧󠁢󠁥󠁮󠁧󠁿 England, " \
109+
"🏴󠁧󠁢󠁳󠁣󠁴󠁿 Scotland, " \
110+
"🏴󠁧󠁢󠁷󠁬󠁳󠁿 Wales.").process(nil)
111+
}
112+
end
113+
114+
def test_anonymous_length_over_255
115+
assert_raise(Net::IMAP::Error) {
116+
anonymous("a" * 256).process(nil)
117+
}
118+
end
119+
83120
# ----------------------
84121
# LOGIN (obsolete)
85122
# ----------------------

0 commit comments

Comments
 (0)