net-imap 0.4.24 → 0.5.0

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.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
3
+ # Net::IMAP authenticator for the +DIGEST-MD5+ SASL mechanism type, specified
4
4
  # in RFC-2831[https://tools.ietf.org/html/rfc2831]. See Net::IMAP#authenticate.
5
5
  #
6
6
  # == Deprecated
@@ -9,11 +9,32 @@
9
9
  # RFC-6331[https://tools.ietf.org/html/rfc6331] and should not be relied on for
10
10
  # security. It is included for compatibility with existing servers.
11
11
  class Net::IMAP::SASL::DigestMD5Authenticator
12
+ DataFormatError = Net::IMAP::DataFormatError
13
+ ResponseParseError = Net::IMAP::ResponseParseError
14
+ private_constant :DataFormatError, :ResponseParseError
15
+
12
16
  STAGE_ONE = :stage_one
13
17
  STAGE_TWO = :stage_two
14
18
  STAGE_DONE = :stage_done
15
19
  private_constant :STAGE_ONE, :STAGE_TWO, :STAGE_DONE
16
20
 
21
+ # Directives which must not have multiples. The RFC states:
22
+ # >>>
23
+ # This directive may appear at most once; if multiple instances are present,
24
+ # the client should abort the authentication exchange.
25
+ NO_MULTIPLES = %w[nonce stale maxbuf charset algorithm].freeze
26
+
27
+ # Required directives which must occur exactly once. The RFC states: >>>
28
+ # This directive is required and MUST appear exactly once; if not present,
29
+ # or if multiple instances are present, the client should abort the
30
+ # authentication exchange.
31
+ REQUIRED = %w[nonce algorithm].freeze
32
+
33
+ # Directives which are composed of one or more comma delimited tokens
34
+ QUOTED_LISTABLE = %w[qop cipher].freeze
35
+
36
+ private_constant :NO_MULTIPLES, :REQUIRED, :QUOTED_LISTABLE
37
+
17
38
  # Authentication identity: the identity that matches the #password.
18
39
  #
19
40
  # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
@@ -42,6 +63,59 @@ class Net::IMAP::SASL::DigestMD5Authenticator
42
63
  #
43
64
  attr_reader :authzid
44
65
 
66
+ # A namespace or collection of identities which contains +username+.
67
+ #
68
+ # Used by DIGEST-MD5, GSS-API, and NTLM. This is often a domain name that
69
+ # contains the name of the host performing the authentication.
70
+ #
71
+ # <em>Defaults to the last realm in the server-provided list of
72
+ # realms.</em>
73
+ attr_reader :realm
74
+
75
+ # Fully qualified canonical DNS host name for the requested service.
76
+ #
77
+ # <em>Defaults to #realm.</em>
78
+ attr_reader :host
79
+
80
+ # The service protocol, a
81
+ # {registered GSSAPI service name}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml],
82
+ # e.g. "imap", "ldap", or "xmpp".
83
+ #
84
+ # For Net::IMAP, the default is "imap" and should not be overridden. This
85
+ # must be set appropriately to use authenticators in other protocols.
86
+ #
87
+ # If an IANA-registered name isn't available, GSS-API
88
+ # (RFC-2743[https://tools.ietf.org/html/rfc2743]) allows the generic name
89
+ # "host".
90
+ attr_reader :service
91
+
92
+ # The generic server name when the server is replicated.
93
+ #
94
+ # +service_name+ will be ignored when it is +nil+ or identical to +host+.
95
+ #
96
+ # From RFC-2831[https://tools.ietf.org/html/rfc2831]:
97
+ # >>>
98
+ # The service is considered to be replicated if the client's
99
+ # service-location process involves resolution using standard DNS lookup
100
+ # operations, and if these operations involve DNS records (such as SRV, or
101
+ # MX) which resolve one DNS name into a set of other DNS names. In this
102
+ # case, the initial name used by the client is the "serv-name", and the
103
+ # final name is the "host" component.
104
+ attr_reader :service_name
105
+
106
+ # Parameters sent by the server are stored in this hash.
107
+ attr_reader :sparams
108
+
109
+ # The charset sent by the server. "UTF-8" (case insensitive) is the only
110
+ # allowed value. +nil+ should be interpreted as ISO 8859-1.
111
+ attr_reader :charset
112
+
113
+ # nonce sent by the server
114
+ attr_reader :nonce
115
+
116
+ # qop-options sent by the server
117
+ attr_reader :qop
118
+
45
119
  # :call-seq:
46
120
  # new(username, password, authzid = nil, **options) -> authenticator
47
121
  # new(username:, password:, authzid: nil, **options) -> authenticator
@@ -64,27 +138,59 @@ class Net::IMAP::SASL::DigestMD5Authenticator
64
138
  # When +authzid+ is not set, the server should derive the authorization
65
139
  # identity from the authentication identity.
66
140
  #
141
+ # * _optional_ #realm — A namespace for the #username, e.g. a domain.
142
+ # <em>Defaults to the last realm in the server-provided realms list.</em>
143
+ # * _optional_ #host — FQDN for requested service.
144
+ # <em>Defaults to</em> #realm.
145
+ # * _optional_ #service_name — The generic host name when the server is
146
+ # replicated.
147
+ # * _optional_ #service — the registered service protocol. E.g. "imap",
148
+ # "smtp", "ldap", "xmpp".
149
+ # <em>For Net::IMAP, this defaults to "imap".</em>
150
+ #
67
151
  # * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning.
68
152
  #
69
153
  # Any other keyword arguments are silently ignored.
70
154
  def initialize(user = nil, pass = nil, authz = nil,
71
155
  username: nil, password: nil, authzid: nil,
72
156
  authcid: nil, secret: nil,
157
+ realm: nil, service: "imap", host: nil, service_name: nil,
73
158
  warn_deprecation: true, **)
74
159
  username = authcid || username || user or
75
160
  raise ArgumentError, "missing username (authcid)"
76
161
  password ||= secret || pass or raise ArgumentError, "missing password"
77
162
  authzid ||= authz
78
163
  if warn_deprecation
79
- warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
80
- # TODO: recommend SCRAM instead.
164
+ warn("WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331.",
165
+ category: :deprecated)
81
166
  end
167
+
82
168
  require "digest/md5"
169
+ require "securerandom"
83
170
  require "strscan"
84
171
  @username, @password, @authzid = username, password, authzid
172
+ @realm = realm
173
+ @host = host
174
+ @service = service
175
+ @service_name = service_name
85
176
  @nc, @stage = {}, STAGE_ONE
86
177
  end
87
178
 
179
+ # From RFC-2831[https://tools.ietf.org/html/rfc2831]:
180
+ # >>>
181
+ # Indicates the principal name of the service with which the client wishes
182
+ # to connect, formed from the serv-type, host, and serv-name. For
183
+ # example, the FTP service on "ftp.example.com" would have a "digest-uri"
184
+ # value of "ftp/ftp.example.com"; the SMTP server from the example above
185
+ # would have a "digest-uri" value of "smtp/mail3.example.com/example.com".
186
+ def digest_uri
187
+ if service_name && service_name != host
188
+ "#{service}/#{host}/#{service_name}"
189
+ else
190
+ "#{service}/#{host}"
191
+ end
192
+ end
193
+
88
194
  def initial_response?; false end
89
195
 
90
196
  # Responds to server challenge in two stages.
@@ -92,78 +198,134 @@ class Net::IMAP::SASL::DigestMD5Authenticator
92
198
  case @stage
93
199
  when STAGE_ONE
94
200
  @stage = STAGE_TWO
95
- sparams = {}
96
- c = StringScanner.new(challenge)
97
- while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]|\\.)*"|[^,]+)\s*/)
98
- k, v = c[1], c[2]
99
- if v =~ /^"(.*)"$/
100
- v = $1
101
- if v =~ /,/
102
- v = v.split(',')
103
- end
104
- end
105
- sparams[k] = v
106
- end
201
+ @sparams = parse_challenge(challenge)
202
+ @qop = sparams.key?("qop") ? ["auth"] : sparams["qop"].flatten
203
+ @nonce = sparams["nonce"] &.first
204
+ @charset = sparams["charset"]&.first
205
+ @realm ||= sparams["realm"] &.last
206
+ @host ||= realm
107
207
 
108
- raise Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos? and sparams['qop']
109
- raise Net::IMAP::Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
208
+ if !qop.include?("auth")
209
+ raise DataFormatError, "Server does not support auth (qop = %p)" % [
210
+ sparams["qop"]
211
+ ]
212
+ elsif (emptykey = REQUIRED.find { sparams[_1].empty? })
213
+ raise DataFormatError, "Server didn't send %s (%p)" % [emptykey, challenge]
214
+ elsif (multikey = NO_MULTIPLES.find { sparams[_1].length > 1 })
215
+ raise DataFormatError, "Server sent multiple %s (%p)" % [multikey, challenge]
216
+ end
110
217
 
111
218
  response = {
112
- :nonce => sparams['nonce'],
113
- :username => @username,
114
- :realm => sparams['realm'],
115
- :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
116
- :'digest-uri' => 'imap/' + sparams['realm'],
117
- :qop => 'auth',
118
- :maxbuf => 65535,
119
- :nc => "%08d" % nc(sparams['nonce']),
120
- :charset => sparams['charset'],
219
+ nonce: nonce,
220
+ username: username,
221
+ realm: realm,
222
+ cnonce: SecureRandom.base64(32),
223
+ "digest-uri": digest_uri,
224
+ qop: "auth",
225
+ maxbuf: 65535,
226
+ nc: "%08d" % nc(nonce),
227
+ charset: charset,
121
228
  }
122
229
 
123
230
  response[:authzid] = @authzid unless @authzid.nil?
124
231
 
125
- # now, the real thing
126
- a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
127
-
128
- a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
129
- a1 << ':' + response[:authzid] unless response[:authzid].nil?
130
-
131
- a2 = "AUTHENTICATE:" + response[:'digest-uri']
132
- a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
133
-
134
- response[:response] = Digest::MD5.hexdigest(
135
- [
136
- Digest::MD5.hexdigest(a1),
137
- response.values_at(:nonce, :nc, :cnonce, :qop),
138
- Digest::MD5.hexdigest(a2)
139
- ].join(':')
140
- )
141
-
142
- return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
232
+ response[:response] = response_value(response)
233
+ format_response(response)
143
234
  when STAGE_TWO
144
235
  @stage = STAGE_DONE
145
- # if at the second stage, return an empty string
146
- if challenge =~ /rspauth=/
147
- return ''
148
- else
149
- raise ResponseParseError, challenge
150
- end
236
+ raise ResponseParseError, challenge unless challenge =~ /rspauth=/
237
+ "" # if at the second stage, return an empty string
151
238
  else
152
239
  raise ResponseParseError, challenge
153
240
  end
241
+ rescue => error
242
+ @stage = error
243
+ raise
154
244
  end
155
245
 
156
246
  def done?; @stage == STAGE_DONE end
157
247
 
158
248
  private
159
249
 
250
+ LWS = /[\r\n \t]*/n # less strict than RFC, more strict than '\s'
251
+ TOKEN = /[^\x00-\x20\x7f()<>@,;:\\"\/\[\]?={}]+/n
252
+ QUOTED_STR = /"(?: [\t\x20-\x7e&&[^"]] | \\[\x00-\x7f] )*"/nx
253
+ LIST_DELIM = /(?:#{LWS} , )+ #{LWS}/nx
254
+ AUTH_PARAM = /
255
+ (#{TOKEN}) #{LWS} = #{LWS} (#{QUOTED_STR} | #{TOKEN}) #{LIST_DELIM}?
256
+ /nx
257
+ private_constant :LWS, :TOKEN, :QUOTED_STR, :LIST_DELIM, :AUTH_PARAM
258
+
259
+ def parse_challenge(challenge)
260
+ sparams = Hash.new {|h, k| h[k] = [] }
261
+ c = StringScanner.new(challenge)
262
+ c.skip LIST_DELIM
263
+ while c.scan AUTH_PARAM
264
+ k, v = c[1], c[2]
265
+ k = k.downcase
266
+ if v =~ /\A"(.*)"\z/mn
267
+ v = $1.gsub(/\\(.)/mn, '\1')
268
+ v = split_quoted_list(v, challenge) if QUOTED_LISTABLE.include? k
269
+ end
270
+ sparams[k] << v
271
+ end
272
+ if !c.eos?
273
+ raise DataFormatError, "Unparsable challenge: %p" % [challenge]
274
+ elsif sparams.empty?
275
+ raise DataFormatError, "Empty challenge: %p" % [challenge]
276
+ end
277
+ sparams
278
+ end
279
+
280
+ def split_quoted_list(value, challenge)
281
+ value.split(LIST_DELIM).reject(&:empty?).tap do
282
+ _1.any? or raise DataFormatError, "Bad Challenge: %p" % [challenge]
283
+ end
284
+ end
285
+
160
286
  def nc(nonce)
161
287
  if @nc.has_key? nonce
162
288
  @nc[nonce] = @nc[nonce] + 1
163
289
  else
164
290
  @nc[nonce] = 1
165
291
  end
166
- return @nc[nonce]
292
+ end
293
+
294
+ def response_value(response)
295
+ a1 = compute_a1(response)
296
+ a2 = compute_a2(response)
297
+ Digest::MD5.hexdigest(
298
+ [
299
+ Digest::MD5.hexdigest(a1),
300
+ response.values_at(:nonce, :nc, :cnonce, :qop),
301
+ Digest::MD5.hexdigest(a2)
302
+ ].join(":")
303
+ )
304
+ end
305
+
306
+ def compute_a0(response)
307
+ Digest::MD5.digest(
308
+ [ response.values_at(:username, :realm), password ].join(":")
309
+ )
310
+ end
311
+
312
+ def compute_a1(response)
313
+ a0 = compute_a0(response)
314
+ a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":")
315
+ a1 << ":#{response[:authzid]}" unless response[:authzid].nil?
316
+ a1
317
+ end
318
+
319
+ def compute_a2(response)
320
+ a2 = "AUTHENTICATE:#{response[:"digest-uri"]}"
321
+ if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
322
+ a2 << ":00000000000000000000000000000000"
323
+ end
324
+ a2
325
+ end
326
+
327
+ def format_response(response)
328
+ response.map {|k, v| qdval(k.to_s, v) }.join(",")
167
329
  end
168
330
 
169
331
  # some responses need quoting
@@ -29,7 +29,8 @@ class Net::IMAP::SASL::LoginAuthenticator
29
29
  warn_deprecation: true,
30
30
  **)
31
31
  if warn_deprecation
32
- warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead."
32
+ warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead.",
33
+ category: :deprecated
33
34
  end
34
35
  @user = authcid || username || user
35
36
  @password = password || secret || pass
@@ -4,16 +4,72 @@ module Net
4
4
  class IMAP
5
5
  module SASL
6
6
 
7
+ # SASL::ProtocolAdapters modules are meant to be used as mixins for
8
+ # SASL::ClientAdapter and its subclasses. Where the client adapter must
9
+ # be customized for each client library, the protocol adapter mixin
10
+ # handles \SASL requirements that are part of the protocol specification,
11
+ # but not specific to any particular client library. In particular, see
12
+ # {RFC4422 §4}[https://www.rfc-editor.org/rfc/rfc4422.html#section-4]
13
+ #
14
+ # === Interface
15
+ #
16
+ # >>>
17
+ # NOTE: This API is experimental, and may change.
18
+ #
19
+ # - {#command_name}[rdoc-ref:Generic#command_name] -- The name of the
20
+ # command used to to initiate an authentication exchange.
21
+ # - {#service}[rdoc-ref:Generic#service] -- The GSSAPI service name.
22
+ # - {#encode_ir}[rdoc-ref:Generic#encode_ir]--Encodes an initial response.
23
+ # - {#decode}[rdoc-ref:Generic#decode] -- Decodes a server challenge.
24
+ # - {#encode}[rdoc-ref:Generic#encode] -- Encodes a client response.
25
+ # - {#cancel_response}[rdoc-ref:Generic#cancel_response] -- The encoded
26
+ # client response used to cancel an authentication exchange.
27
+ #
28
+ # Other protocol requirements of the \SASL authentication exchange are
29
+ # handled by SASL::ClientAdapter.
30
+ #
31
+ # === Included protocol adapters
32
+ #
33
+ # - Generic -- a basic implementation of all of the methods listed above.
34
+ # - IMAP -- An adapter for the IMAP4 protocol.
35
+ # - SMTP -- An adapter for the \SMTP protocol with the +AUTH+ capability.
36
+ # - POP -- An adapter for the POP3 protocol with the +SASL+ capability.
7
37
  module ProtocolAdapters
8
- # This API is experimental, and may change.
38
+ # See SASL::ProtocolAdapters@Interface.
9
39
  module Generic
40
+ # The name of the protocol command used to initiate a \SASL
41
+ # authentication exchange.
42
+ #
43
+ # The generic implementation returns <tt>"AUTHENTICATE"</tt>.
10
44
  def command_name; "AUTHENTICATE" end
11
- def service; raise "Implement in subclass or module" end
12
- def host; client.host end
13
- def port; client.port end
45
+
46
+ # A service name from the {GSSAPI/Kerberos/SASL Service Names
47
+ # registry}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml].
48
+ #
49
+ # The generic implementation returns <tt>"host"</tt>, which is the
50
+ # generic GSSAPI host-based service name.
51
+ def service; "host" end
52
+
53
+ # Encodes an initial response string.
54
+ #
55
+ # The generic implementation returns the result of #encode, or returns
56
+ # <tt>"="</tt> when +string+ is empty.
14
57
  def encode_ir(string) string.empty? ? "=" : encode(string) end
58
+
59
+ # Encodes a client response string.
60
+ #
61
+ # The generic implementation returns the Base64 encoding of +string+.
15
62
  def encode(string) [string].pack("m0") end
63
+
64
+ # Decodes a server challenge string.
65
+ #
66
+ # The generic implementation returns the Base64 decoding of +string+.
16
67
  def decode(string) string.unpack1("m0") end
68
+
69
+ # Returns the message used by the client to abort an authentication
70
+ # exchange.
71
+ #
72
+ # The generic implementation returns <tt>"*"</tt>.
17
73
  def cancel_response; "*" end
18
74
  end
19
75
 
@@ -75,19 +75,13 @@ module Net
75
75
  # * #password ― Password or passphrase associated with this #username.
76
76
  # * _optional_ #authzid ― Alternate identity to act as or on behalf of.
77
77
  # * _optional_ #min_iterations - Overrides the default value (4096).
78
- # * _optional_ #max_iterations - Overrides the default value (2³¹ - 1).
79
78
  #
80
79
  # Any other keyword parameters are quietly ignored.
81
- #
82
- # *NOTE:* <em>It is the user's responsibility</em> to enforce minimum
83
- # and maximum iteration counts that are appropriate for their security
84
- # context.
85
80
  def initialize(username_arg = nil, password_arg = nil,
86
81
  authcid: nil, username: nil,
87
82
  authzid: nil,
88
83
  password: nil, secret: nil,
89
84
  min_iterations: 4096, # see both RFC5802 and RFC7677
90
- max_iterations: 2**31 - 1, # max int32
91
85
  cnonce: nil, # must only be set in tests
92
86
  **options)
93
87
  @username = username || username_arg || authcid or
@@ -100,22 +94,7 @@ module Net
100
94
  @min_iterations.positive? or
101
95
  raise ArgumentError, "min_iterations must be positive"
102
96
 
103
- @max_iterations = Integer max_iterations.to_int
104
- @min_iterations <= @max_iterations or
105
- raise ArgumentError, "max_iterations must be more than min_iterations"
106
-
107
97
  @cnonce = cnonce || SecureRandom.base64(32)
108
-
109
- # These attrs are set from the server challenges
110
- @server_first_message = @snonce = @salt = @iterations = nil
111
- @server_error = nil
112
-
113
- # Memoized after @salt and @iterations have been sent.
114
- @salted_password = @client_key = @server_key = nil
115
-
116
- # These values are created and cached in response to server challenges
117
- @client_first_message_bare = nil
118
- @client_final_message_without_proof = nil
119
98
  end
120
99
 
121
100
  # Authentication identity: the identity that matches the #password.
@@ -148,43 +127,8 @@ module Net
148
127
 
149
128
  # The minimal allowed iteration count. Lower #iterations will raise an
150
129
  # Error.
151
- #
152
- # *WARNING:* The default value (4096) is set to match guidance from
153
- # both {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#page-12]
154
- # and RFC7677[https://www.rfc-editor.org/rfc/rfc7677#section-4], but
155
- # {modern recommendations}[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2]
156
- # are significantly higher.
157
- #
158
- # It is ultimately the server's responsibility to securely store
159
- # password hashes. While this parameter can alert the user to
160
- # insecure password storage and prevent insecure authentication
161
- # exchange, updating the iteration count generally requires resetting
162
- # the password on the server.
163
130
  attr_reader :min_iterations
164
131
 
165
- # The maximal allowed iteration count. Higher #iterations will raise an
166
- # Error.
167
- #
168
- # As noted in {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#section-9]
169
- # >>>
170
- # A hostile server can perform a computational denial-of-service
171
- # attack on clients by sending a big iteration count value.
172
- #
173
- # *WARNING:* The default value is <tt>2³¹ - 1</tt>, the maximum signed
174
- # 32-bit integer. This is large enough for the computation to take
175
- # several minutes, and insufficient protection against hostile servers.
176
- #
177
- # Note that <tt>OpenSSL::KDF.pbkdf2_hmac</tt> is implemented by a
178
- # blocking C function, and cannot be interrupted by +Timeout+ or
179
- # <tt>Thread.raise</tt>. And it keeps the Global VM lock, as of v4.0 of
180
- # the +openssl+ gem, so other ruby threads will not be able to run.
181
- #
182
- # <em>To prevent a denial of service attack,</em> this must be set to a
183
- # safe value, depending on hardware and version of OpenSSL. <em>It is
184
- # the user's responsibility</em> to enforce minimum and maximum
185
- # iteration counts that are appropriate for their security context.
186
- attr_reader :max_iterations
187
-
188
132
  # The client nonce, generated by SecureRandom
189
133
  attr_reader :cnonce
190
134
 
@@ -203,15 +147,6 @@ module Net
203
147
  # Net::IMAP::NoResponseError.
204
148
  attr_reader :server_error
205
149
 
206
- # Memoized ScramAlgorithm#salted_password (needs #salt and #iterations)
207
- def salted_password; @salted_password ||= compute_salted { super } end
208
-
209
- # Memoized ScramAlgorithm#client_key (needs #salt and #iterations)
210
- def client_key; @client_key ||= compute_salted { super } end
211
-
212
- # Memoized ScramAlgorithm#server_key (needs #salt and #iterations)
213
- def server_key; @server_key ||= compute_salted { super } end
214
-
215
150
  # Returns a new OpenSSL::Digest object, set to the appropriate hash
216
151
  # function for the chosen mechanism.
217
152
  #
@@ -251,13 +186,6 @@ module Net
251
186
 
252
187
  private
253
188
 
254
- # Checks for +salt+ and +iterations+ before yielding
255
- def compute_salted
256
- String === salt or raise Error, "unknown salt"
257
- Integer === iterations or raise Error, "unknown iterations"
258
- yield
259
- end
260
-
261
189
  # Need to store this for auth_message
262
190
  attr_reader :server_first_message
263
191
 
@@ -274,8 +202,6 @@ module Net
274
202
  raise Error, "server did not send iteration count"
275
203
  min_iterations <= iterations or
276
204
  raise Error, "too few iterations: %d" % [iterations]
277
- max_iterations.nil? || iterations <= max_iterations or
278
- raise Error, "too many iterations: %d" % [iterations]
279
205
  mext = sparams["m"] and
280
206
  raise Error, "mandatory extension: %p" % [mext]
281
207
  snonce.start_with? cnonce or
data/lib/net/imap/sasl.rb CHANGED
@@ -114,8 +114,8 @@ module Net
114
114
  # messages has not passed integrity checks.
115
115
  AuthenticationFailed = Class.new(Error)
116
116
 
117
- # Indicates that authentication cannot proceed because one of the server's
118
- # ended authentication prematurely.
117
+ # Indicates that authentication cannot proceed because the server ended
118
+ # authentication prematurely.
119
119
  class AuthenticationIncomplete < AuthenticationFailed
120
120
  # The success response from the server
121
121
  attr_reader :response
@@ -159,7 +159,10 @@ module Net
159
159
  # Returns the default global SASL::Authenticators instance.
160
160
  def self.authenticators; @authenticators ||= Authenticators.new end
161
161
 
162
- # Delegates to <tt>registry.new</tt> See Authenticators#new.
162
+ # Creates a new SASL authenticator, using SASL::Authenticators#new.
163
+ #
164
+ # +registry+ defaults to SASL.authenticators. All other arguments are
165
+ # forwarded to to <tt>registry.new</tt>.
163
166
  def self.authenticator(*args, registry: authenticators, **kwargs, &block)
164
167
  registry.new(*args, **kwargs, &block)
165
168
  end
@@ -12,7 +12,6 @@ module Net
12
12
 
13
13
  def response_errors; RESPONSE_ERRORS end
14
14
  def sasl_ir_capable?; client.capable?("SASL-IR") end
15
- def auth_capable?(mechanism); client.auth_capable?(mechanism) end
16
15
  def drop_connection; client.logout! end
17
16
  def drop_connection!; client.disconnect end
18
17
  end