net-imap 0.3.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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 +21 -9
  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 -31
  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