net-imap 0.3.7 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/BSDL +22 -0
- data/COPYING +56 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +3 -22
- data/README.md +25 -8
- data/Rakefile +0 -7
- data/docs/styles.css +72 -23
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +74 -54
- data/lib/net/imap/config/attr_accessors.rb +75 -0
- data/lib/net/imap/config/attr_inheritance.rb +90 -0
- data/lib/net/imap/config/attr_type_coercion.rb +61 -0
- data/lib/net/imap/config.rb +470 -0
- data/lib/net/imap/data_encoding.rb +18 -6
- data/lib/net/imap/data_lite.rb +226 -0
- data/lib/net/imap/deprecated_client_options.rb +142 -0
- data/lib/net/imap/errors.rb +27 -1
- data/lib/net/imap/esearch_result.rb +180 -0
- data/lib/net/imap/fetch_data.rb +597 -0
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +250 -440
- data/lib/net/imap/response_parser/parser_utils.rb +245 -0
- data/lib/net/imap/response_parser.rb +1867 -1184
- data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
- data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
- data/lib/net/imap/sasl/authenticators.rb +122 -0
- data/lib/net/imap/sasl/client_adapter.rb +123 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
- data/lib/net/imap/sasl/external_authenticator.rb +83 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
- data/lib/net/imap/sasl.rb +148 -44
- data/lib/net/imap/sasl_adapter.rb +20 -0
- data/lib/net/imap/search_result.rb +146 -0
- data/lib/net/imap/sequence_set.rb +1565 -0
- data/lib/net/imap/stringprep/nameprep.rb +70 -0
- data/lib/net/imap/stringprep/saslprep.rb +69 -0
- data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
- data/lib/net/imap/stringprep/tables.rb +146 -0
- data/lib/net/imap/stringprep/trace.rb +85 -0
- data/lib/net/imap/stringprep.rb +159 -0
- data/lib/net/imap/uidplus_data.rb +244 -0
- data/lib/net/imap/vanished_data.rb +56 -0
- data/lib/net/imap.rb +2090 -823
- data/net-imap.gemspec +7 -8
- data/rakelib/benchmarks.rake +91 -0
- data/rakelib/rfcs.rake +2 -0
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +84 -60
- data/sample/net-imap.rb +167 -0
- metadata +45 -49
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/test.yml +0 -38
- data/.gitignore +0 -10
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
- data/lib/net/imap/authenticators/digest_md5.rb +0 -115
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- data/lib/net/imap/sasl/saslprep.rb +0 -55
- data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
- data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# Authenticator for the "+ANONYMOUS+" SASL mechanism, as specified by
|
8
|
+
# RFC-4505[https://www.rfc-editor.org/rfc/rfc4505]. See
|
9
|
+
# Net::IMAP#authenticate.
|
10
|
+
class AnonymousAuthenticator
|
11
|
+
|
12
|
+
# An optional token sent for the +ANONYMOUS+ mechanism., up to 255 UTF-8
|
13
|
+
# characters in length.
|
14
|
+
#
|
15
|
+
# If it contains an "@" sign, the message must be a valid email address
|
16
|
+
# (+addr-spec+ from RFC-2822[https://www.rfc-editor.org/rfc/rfc2822]).
|
17
|
+
# Email syntax is _not_ validated by AnonymousAuthenticator.
|
18
|
+
#
|
19
|
+
# Otherwise, it can be any UTF8 string which is permitted by the
|
20
|
+
# StringPrep::Trace profile.
|
21
|
+
attr_reader :anonymous_message
|
22
|
+
|
23
|
+
# :call-seq:
|
24
|
+
# new(anonymous_message = "", **) -> authenticator
|
25
|
+
# new(anonymous_message: "", **) -> authenticator
|
26
|
+
#
|
27
|
+
# Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as
|
28
|
+
# specified in RFC-4505[https://www.rfc-editor.org/rfc/rfc4505]. To use
|
29
|
+
# this, see Net::IMAP#authenticate or your client's authentication
|
30
|
+
# method.
|
31
|
+
#
|
32
|
+
# ==== Parameters
|
33
|
+
#
|
34
|
+
# * _optional_ #anonymous_message — a message to send to the server.
|
35
|
+
#
|
36
|
+
# Any other keyword arguments are silently ignored.
|
37
|
+
def initialize(anon_msg = nil, anonymous_message: nil, **)
|
38
|
+
message = (anonymous_message || anon_msg || "").to_str
|
39
|
+
@anonymous_message = StringPrep::Trace.stringprep_trace message
|
40
|
+
if (size = @anonymous_message&.length)&.> 255
|
41
|
+
raise ArgumentError,
|
42
|
+
"anonymous_message is too long. (%d codepoints)" % [size]
|
43
|
+
end
|
44
|
+
@done = false
|
45
|
+
end
|
46
|
+
|
47
|
+
# :call-seq:
|
48
|
+
# initial_response? -> true
|
49
|
+
#
|
50
|
+
# +ANONYMOUS+ can send an initial client response.
|
51
|
+
def initial_response?; true end
|
52
|
+
|
53
|
+
# Returns #anonymous_message.
|
54
|
+
def process(_server_challenge_string)
|
55
|
+
anonymous_message
|
56
|
+
ensure
|
57
|
+
@done = true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns true when the initial client response was sent.
|
61
|
+
#
|
62
|
+
# The authentication should not succeed unless this returns true, but it
|
63
|
+
# does *not* indicate success.
|
64
|
+
def done?; @done end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# AuthenticationExchange is used internally by Net::IMAP#authenticate.
|
8
|
+
# But the API is still *experimental*, and may change.
|
9
|
+
#
|
10
|
+
# TODO: catch exceptions in #process and send #cancel_response.
|
11
|
+
# TODO: raise an error if the command succeeds after being canceled.
|
12
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
13
|
+
# TODO: pass ClientAdapter#service to SASL.authenticator
|
14
|
+
#
|
15
|
+
# An AuthenticationExchange represents a single attempt to authenticate
|
16
|
+
# a SASL client to a SASL server. It is created from a client adapter, a
|
17
|
+
# mechanism name, and a mechanism authenticator. When #authenticate is
|
18
|
+
# called, it will send the appropriate authenticate command to the server,
|
19
|
+
# returning the client response on success and raising an exception on
|
20
|
+
# failure.
|
21
|
+
#
|
22
|
+
# In most cases, the client will not need to use
|
23
|
+
# SASL::AuthenticationExchange directly at all. Instead, use
|
24
|
+
# SASL::ClientAdapter#authenticate. If customizations are needed, the
|
25
|
+
# custom client adapter is probably the best place for that code.
|
26
|
+
#
|
27
|
+
# def authenticate(...)
|
28
|
+
# MyClient::SASLAdapter.new(self).authenticate(...)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# SASL::ClientAdapter#authenticate delegates to ::authenticate, like so:
|
32
|
+
#
|
33
|
+
# def authenticate(...)
|
34
|
+
# sasl_adapter = MyClient::SASLAdapter.new(self)
|
35
|
+
# SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# ::authenticate simply delegates to ::build and #authenticate, like so:
|
39
|
+
#
|
40
|
+
# def authenticate(...)
|
41
|
+
# sasl_adapter = MyClient::SASLAdapter.new(self)
|
42
|
+
# SASL::AuthenticationExchange
|
43
|
+
# .build(sasl_adapter, ...)
|
44
|
+
# .authenticate
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# And ::build delegates to SASL.authenticator and ::new, like so:
|
48
|
+
#
|
49
|
+
# def authenticate(mechanism, ...)
|
50
|
+
# sasl_adapter = MyClient::SASLAdapter.new(self)
|
51
|
+
# authenticator = SASL.authenticator(mechanism, ...)
|
52
|
+
# SASL::AuthenticationExchange
|
53
|
+
# .new(sasl_adapter, mechanism, authenticator)
|
54
|
+
# .authenticate
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
class AuthenticationExchange
|
58
|
+
# Convenience method for <tt>build(...).authenticate</tt>
|
59
|
+
#
|
60
|
+
# See also: SASL::ClientAdapter#authenticate
|
61
|
+
def self.authenticate(...) build(...).authenticate end
|
62
|
+
|
63
|
+
# Convenience method to combine the creation of a new authenticator and
|
64
|
+
# a new Authentication exchange.
|
65
|
+
#
|
66
|
+
# +client+ must be an instance of SASL::ClientAdapter.
|
67
|
+
#
|
68
|
+
# +mechanism+ must be a SASL mechanism name, as a string or symbol.
|
69
|
+
#
|
70
|
+
# +sasl_ir+ allows or disallows sending an "initial response", depending
|
71
|
+
# also on whether the server capabilities, mechanism authenticator, and
|
72
|
+
# client adapter all support it. Defaults to +true+.
|
73
|
+
#
|
74
|
+
# +mechanism+, +args+, +kwargs+, and +block+ are all forwarded to
|
75
|
+
# SASL.authenticator. Use the +registry+ kwarg to override the global
|
76
|
+
# SASL::Authenticators registry.
|
77
|
+
def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
|
78
|
+
authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
|
79
|
+
new(client, mechanism, authenticator, sasl_ir: sasl_ir)
|
80
|
+
end
|
81
|
+
|
82
|
+
attr_reader :mechanism, :authenticator
|
83
|
+
|
84
|
+
def initialize(client, mechanism, authenticator, sasl_ir: true)
|
85
|
+
@client = client
|
86
|
+
@mechanism = Authenticators.normalize_name(mechanism)
|
87
|
+
@authenticator = authenticator
|
88
|
+
@sasl_ir = sasl_ir
|
89
|
+
@processed = false
|
90
|
+
end
|
91
|
+
|
92
|
+
# Call #authenticate to execute an authentication exchange for #client
|
93
|
+
# using #authenticator. Authentication failures will raise an
|
94
|
+
# exception. Any exceptions other than those in RESPONSE_ERRORS will
|
95
|
+
# drop the connection.
|
96
|
+
def authenticate
|
97
|
+
client.run_command(mechanism, initial_response) { process _1 }
|
98
|
+
.tap { raise AuthenticationIncomplete, _1 unless done? }
|
99
|
+
rescue *client.response_errors
|
100
|
+
raise # but don't drop the connection
|
101
|
+
rescue
|
102
|
+
client.drop_connection
|
103
|
+
raise
|
104
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
105
|
+
client.drop_connection!
|
106
|
+
raise
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_initial_response?
|
110
|
+
@sasl_ir &&
|
111
|
+
authenticator.respond_to?(:initial_response?) &&
|
112
|
+
authenticator.initial_response? &&
|
113
|
+
client.sasl_ir_capable? &&
|
114
|
+
client.auth_capable?(mechanism)
|
115
|
+
end
|
116
|
+
|
117
|
+
def done?
|
118
|
+
authenticator.respond_to?(:done?) ? authenticator.done? : @processed
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :client
|
124
|
+
|
125
|
+
def initial_response
|
126
|
+
return unless send_initial_response?
|
127
|
+
client.encode_ir authenticator.process nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def process(challenge)
|
131
|
+
client.encode authenticator.process client.decode challenge
|
132
|
+
ensure
|
133
|
+
@processed = true
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net::IMAP::SASL
|
4
|
+
|
5
|
+
# Registry for SASL authenticators
|
6
|
+
#
|
7
|
+
# Registered authenticators must respond to +#new+ or +#call+ (e.g. a class or
|
8
|
+
# a proc), receiving any credentials and options and returning an
|
9
|
+
# authenticator instance. The returned object represents a single
|
10
|
+
# authentication exchange and <em>must not</em> be reused for multiple
|
11
|
+
# authentication attempts.
|
12
|
+
#
|
13
|
+
# An authenticator instance object must respond to +#process+, receiving the
|
14
|
+
# server's challenge and returning the client's response. Optionally, it may
|
15
|
+
# also respond to +#initial_response?+ and +#done?+. When
|
16
|
+
# +#initial_response?+ returns +true+, +#process+ may be called the first
|
17
|
+
# time with +nil+. When +#done?+ returns +false+, the exchange is incomplete
|
18
|
+
# and an exception should be raised if the exchange terminates prematurely.
|
19
|
+
#
|
20
|
+
# See the source for PlainAuthenticator, XOAuth2Authenticator, and
|
21
|
+
# ScramSHA1Authenticator for examples.
|
22
|
+
class Authenticators
|
23
|
+
|
24
|
+
# Normalize the mechanism name as an uppercase string, with underscores
|
25
|
+
# converted to dashes.
|
26
|
+
def self.normalize_name(mechanism) -(mechanism.to_s.upcase.tr(?_, ?-)) end
|
27
|
+
|
28
|
+
# Create a new Authenticators registry.
|
29
|
+
#
|
30
|
+
# This class is usually not instantiated directly. Use SASL.authenticators
|
31
|
+
# to reuse the default global registry.
|
32
|
+
#
|
33
|
+
# When +use_defaults+ is +false+, the registry will start empty. When
|
34
|
+
# +use_deprecated+ is +false+, deprecated authenticators will not be
|
35
|
+
# included with the defaults.
|
36
|
+
def initialize(use_defaults: true, use_deprecated: true)
|
37
|
+
@authenticators = {}
|
38
|
+
return unless use_defaults
|
39
|
+
add_authenticator "Anonymous"
|
40
|
+
add_authenticator "External"
|
41
|
+
add_authenticator "OAuthBearer"
|
42
|
+
add_authenticator "Plain"
|
43
|
+
add_authenticator "Scram-SHA-1"
|
44
|
+
add_authenticator "Scram-SHA-256"
|
45
|
+
add_authenticator "XOAuth2"
|
46
|
+
return unless use_deprecated
|
47
|
+
add_authenticator "Login" # deprecated
|
48
|
+
add_authenticator "Cram-MD5" # deprecated
|
49
|
+
add_authenticator "Digest-MD5" # deprecated
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the names of all registered SASL mechanisms.
|
53
|
+
def names; @authenticators.keys end
|
54
|
+
|
55
|
+
# :call-seq:
|
56
|
+
# add_authenticator(mechanism)
|
57
|
+
# add_authenticator(mechanism, authenticator_class)
|
58
|
+
# add_authenticator(mechanism, authenticator_proc)
|
59
|
+
#
|
60
|
+
# Registers an authenticator for #authenticator to use. +mechanism+ is the
|
61
|
+
# name of the
|
62
|
+
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
63
|
+
# implemented by +authenticator_class+ (for instance, <tt>"PLAIN"</tt>).
|
64
|
+
#
|
65
|
+
# If +mechanism+ refers to an existing authenticator,
|
66
|
+
# the old authenticator will be replaced.
|
67
|
+
#
|
68
|
+
# When only a single argument is given, the authenticator class will be
|
69
|
+
# lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
|
70
|
+
# preserved and non-alphanumeric characters are removed..
|
71
|
+
def add_authenticator(name, authenticator = nil)
|
72
|
+
authenticator ||= begin
|
73
|
+
class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
|
74
|
+
auth_class = nil
|
75
|
+
->(*creds, **props, &block) {
|
76
|
+
auth_class ||= Net::IMAP::SASL.const_get(class_name)
|
77
|
+
auth_class.new(*creds, **props, &block)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
key = Authenticators.normalize_name(name)
|
81
|
+
@authenticators[key] = authenticator
|
82
|
+
end
|
83
|
+
|
84
|
+
# Removes the authenticator registered for +name+
|
85
|
+
def remove_authenticator(name)
|
86
|
+
key = Authenticators.normalize_name(name)
|
87
|
+
@authenticators.delete(key)
|
88
|
+
end
|
89
|
+
|
90
|
+
def mechanism?(name)
|
91
|
+
key = Authenticators.normalize_name(name)
|
92
|
+
@authenticators.key?(key)
|
93
|
+
end
|
94
|
+
|
95
|
+
# :call-seq:
|
96
|
+
# authenticator(mechanism, ...) -> auth_session
|
97
|
+
#
|
98
|
+
# Builds an authenticator instance using the authenticator registered to
|
99
|
+
# +mechanism+. The returned object represents a single authentication
|
100
|
+
# exchange and <em>must not</em> be reused for multiple authentication
|
101
|
+
# attempts.
|
102
|
+
#
|
103
|
+
# All arguments (except +mechanism+) are forwarded to the registered
|
104
|
+
# authenticator's +#new+ or +#call+ method. Each authenticator must
|
105
|
+
# document its own arguments.
|
106
|
+
#
|
107
|
+
# [Note]
|
108
|
+
# This method is intended for internal use by connection protocol code
|
109
|
+
# only. Protocol client users should see refer to their client's
|
110
|
+
# documentation, e.g. Net::IMAP#authenticate.
|
111
|
+
def authenticator(mechanism, ...)
|
112
|
+
key = Authenticators.normalize_name(mechanism)
|
113
|
+
auth = @authenticators.fetch(key) do
|
114
|
+
raise ArgumentError, 'unknown auth type - "%s"' % key
|
115
|
+
end
|
116
|
+
auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
|
117
|
+
end
|
118
|
+
alias new authenticator
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Net
|
6
|
+
class IMAP
|
7
|
+
module SASL
|
8
|
+
|
9
|
+
# This API is *experimental*, and may change.
|
10
|
+
#
|
11
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
12
|
+
#
|
13
|
+
# Represents the client to a SASL::AuthenticationExchange. By default,
|
14
|
+
# most methods simply delegate to #client. Clients should subclass
|
15
|
+
# SASL::ClientAdapter and override methods as needed to match the
|
16
|
+
# semantics of this API to their API.
|
17
|
+
#
|
18
|
+
# Subclasses should also include a protocol adapter mixin when the default
|
19
|
+
# ProtocolAdapters::Generic isn't sufficient.
|
20
|
+
#
|
21
|
+
# === Protocol Requirements
|
22
|
+
#
|
23
|
+
# {RFC4422 §4}[https://www.rfc-editor.org/rfc/rfc4422.html#section-4]
|
24
|
+
# lists requirements for protocol specifications to offer SASL. Where
|
25
|
+
# possible, ClientAdapter delegates the handling of these requirements to
|
26
|
+
# SASL::ProtocolAdapters.
|
27
|
+
class ClientAdapter
|
28
|
+
extend Forwardable
|
29
|
+
|
30
|
+
include ProtocolAdapters::Generic
|
31
|
+
|
32
|
+
# The client that handles communication with the protocol server.
|
33
|
+
#
|
34
|
+
# Most ClientAdapter methods are simply delegated to #client by default.
|
35
|
+
attr_reader :client
|
36
|
+
|
37
|
+
# +command_proc+ can used to avoid exposing private methods on #client.
|
38
|
+
# It's value is set by the block that is passed to ::new, and it is used
|
39
|
+
# by the default implementation of #run_command. Subclasses that
|
40
|
+
# override #run_command may use #command_proc for any other purpose they
|
41
|
+
# find useful.
|
42
|
+
#
|
43
|
+
# In the default implementation of #run_command, command_proc is called
|
44
|
+
# with the protocols authenticate +command+ name, the +mechanism+ name,
|
45
|
+
# an _optional_ +initial_response+ argument, and a +continuations+
|
46
|
+
# block. command_proc must run the protocol command with the arguments
|
47
|
+
# sent to it, _yield_ the payload of each continuation, respond to the
|
48
|
+
# continuation with the result of each _yield_, and _return_ the
|
49
|
+
# command's successful result. Non-successful results *MUST* raise
|
50
|
+
# an exception.
|
51
|
+
attr_reader :command_proc
|
52
|
+
|
53
|
+
# By default, this simply sets the #client and #command_proc attributes.
|
54
|
+
# Subclasses may override it, for example: to set the appropriate
|
55
|
+
# command_proc automatically.
|
56
|
+
def initialize(client, &command_proc)
|
57
|
+
@client, @command_proc = client, command_proc
|
58
|
+
end
|
59
|
+
|
60
|
+
# Attempt to authenticate #client to the server.
|
61
|
+
#
|
62
|
+
# By default, this simply delegates to
|
63
|
+
# AuthenticationExchange.authenticate.
|
64
|
+
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
|
65
|
+
|
66
|
+
##
|
67
|
+
# method: sasl_ir_capable?
|
68
|
+
# Do the protocol, server, and client all support an initial response?
|
69
|
+
def_delegator :client, :sasl_ir_capable?
|
70
|
+
|
71
|
+
##
|
72
|
+
# method: auth_capable?
|
73
|
+
# call-seq: auth_capable?(mechanism)
|
74
|
+
#
|
75
|
+
# Does the server advertise support for the +mechanism+?
|
76
|
+
def_delegator :client, :auth_capable?
|
77
|
+
|
78
|
+
# Calls command_proc with +command_name+ (see
|
79
|
+
# SASL::ProtocolAdapters::Generic#command_name),
|
80
|
+
# +mechanism+, +initial_response+, and a +continuations_handler+ block.
|
81
|
+
# The +initial_response+ is optional; when it's nil, it won't be sent to
|
82
|
+
# command_proc.
|
83
|
+
#
|
84
|
+
# Yields each continuation payload, responds to the server with the
|
85
|
+
# result of each yield, and returns the result. Non-successful results
|
86
|
+
# *MUST* raise an exception. Exceptions in the block *MUST* cause the
|
87
|
+
# command to fail.
|
88
|
+
#
|
89
|
+
# Subclasses that override this may use #command_proc differently.
|
90
|
+
def run_command(mechanism, initial_response = nil, &continuations_handler)
|
91
|
+
command_proc or raise Error, "initialize with block or override"
|
92
|
+
args = [command_name, mechanism, initial_response].compact
|
93
|
+
command_proc.call(*args, &continuations_handler)
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# method: host
|
98
|
+
# The hostname to which the client connected.
|
99
|
+
def_delegator :client, :host
|
100
|
+
|
101
|
+
##
|
102
|
+
# method: port
|
103
|
+
# The destination port to which the client connected.
|
104
|
+
def_delegator :client, :port
|
105
|
+
|
106
|
+
# Returns an array of server responses errors raised by run_command.
|
107
|
+
# Exceptions in this array won't drop the connection.
|
108
|
+
def response_errors; [] end
|
109
|
+
|
110
|
+
##
|
111
|
+
# method: drop_connection
|
112
|
+
# Drop the connection gracefully, sending a "LOGOUT" command as needed.
|
113
|
+
def_delegator :client, :drop_connection
|
114
|
+
|
115
|
+
##
|
116
|
+
# method: drop_connection!
|
117
|
+
# Drop the connection abruptly, closing the socket without logging out.
|
118
|
+
def_delegator :client, :drop_connection!
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,34 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
|
4
|
-
# RFC2195[https://
|
4
|
+
# RFC2195[https://www.rfc-editor.org/rfc/rfc2195]. See Net::IMAP#authenticate.
|
5
5
|
#
|
6
6
|
# == Deprecated
|
7
7
|
#
|
8
8
|
# +CRAM-MD5+ is obsolete and insecure. It is included for compatibility with
|
9
9
|
# existing servers.
|
10
|
-
# {draft-ietf-sasl-crammd5-to-historic}[https://
|
10
|
+
# {draft-ietf-sasl-crammd5-to-historic}[https://www.rfc-editor.org/rfc/draft-ietf-sasl-crammd5-to-historic-00.html]
|
11
11
|
# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead.
|
12
12
|
#
|
13
|
-
# Additionally, RFC8314[https://
|
13
|
+
# Additionally, RFC8314[https://www.rfc-editor.org/rfc/rfc8314] discourage the use
|
14
14
|
# of cleartext and recommends TLS version 1.2 or greater be used for all
|
15
15
|
# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
|
16
|
-
class Net::IMAP::CramMD5Authenticator
|
16
|
+
class Net::IMAP::SASL::CramMD5Authenticator
|
17
|
+
def initialize(user = nil, pass = nil,
|
18
|
+
authcid: nil, username: nil,
|
19
|
+
password: nil, secret: nil,
|
20
|
+
warn_deprecation: true,
|
21
|
+
**)
|
22
|
+
if warn_deprecation
|
23
|
+
warn "WARNING: CRAM-MD5 mechanism is deprecated.", category: :deprecated
|
24
|
+
end
|
25
|
+
require "digest/md5"
|
26
|
+
@user = authcid || username || user
|
27
|
+
@password = password || secret || pass
|
28
|
+
@done = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def initial_response?; false end
|
32
|
+
|
17
33
|
def process(challenge)
|
18
34
|
digest = hmac_md5(challenge, @password)
|
19
35
|
return @user + " " + digest
|
36
|
+
ensure
|
37
|
+
@done = true
|
20
38
|
end
|
21
39
|
|
22
|
-
|
40
|
+
def done?; @done end
|
23
41
|
|
24
|
-
|
25
|
-
if warn_deprecation
|
26
|
-
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
|
27
|
-
end
|
28
|
-
require "digest/md5"
|
29
|
-
@user = user
|
30
|
-
@password = password
|
31
|
-
end
|
42
|
+
private
|
32
43
|
|
33
44
|
def hmac_md5(text, key)
|
34
45
|
if key.length > 64
|
@@ -47,5 +58,4 @@ class Net::IMAP::CramMD5Authenticator
|
|
47
58
|
return Digest::MD5.hexdigest(k_opad + digest)
|
48
59
|
end
|
49
60
|
|
50
|
-
Net::IMAP.add_authenticator "CRAM-MD5", self
|
51
61
|
end
|