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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +18 -6
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -38
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. 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://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate.
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://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html]
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://tools.ietf.org/html/rfc8314] discourage the use
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
- private
40
+ def done?; @done end
23
41
 
24
- def initialize(user, password, warn_deprecation: true, **_ignored)
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