net-imap 0.4.19 → 0.4.24

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.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ # See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
6
+ class ResponseReader # :nodoc:
7
+ attr_reader :client
8
+
9
+ def initialize(client, sock)
10
+ @client, @sock = client, sock
11
+ @buff = @literal_size = nil
12
+ end
13
+
14
+ def read_response_buffer
15
+ @buff = String.new
16
+ catch :eof do
17
+ while true
18
+ read_line
19
+ break unless literal_size
20
+ read_literal
21
+ end
22
+ end
23
+ buff
24
+ ensure
25
+ @buff = @literal_size = nil
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :buff, :literal_size
31
+
32
+ def bytes_read; buff.bytesize end
33
+ def empty?; buff.empty? end
34
+ def done?; line_done? && !get_literal_size end
35
+ def done?; line_done? && !literal_size end
36
+ def line_done?; buff.end_with?(CRLF) end
37
+
38
+ def get_literal_size(buff)
39
+ buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i
40
+ end
41
+
42
+ def read_line
43
+ line = (@sock.gets(CRLF, read_limit) or throw :eof)
44
+ buff << line
45
+ max_response_remaining! unless line_done?
46
+ @literal_size = get_literal_size(line)
47
+ end
48
+
49
+ def read_literal
50
+ # check before allocating memory for literal
51
+ max_response_remaining!
52
+ literal = String.new(capacity: literal_size)
53
+ buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
54
+ ensure
55
+ @literal_size = nil
56
+ end
57
+
58
+ def read_limit(limit = nil)
59
+ [limit, max_response_remaining!].compact.min
60
+ end
61
+
62
+ def max_response_size; client.max_response_size end
63
+ def max_response_remaining; max_response_size &.- bytes_read end
64
+ def response_too_large?; max_response_size &.< min_response_size end
65
+ def min_response_size; bytes_read + min_response_remaining end
66
+
67
+ def min_response_remaining
68
+ empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
69
+ end
70
+
71
+ def max_response_remaining!
72
+ return max_response_remaining unless response_too_large?
73
+ raise ResponseTooLargeError.new(
74
+ max_response_size: max_response_size,
75
+ bytes_read: bytes_read,
76
+ literal_size: literal_size,
77
+ )
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -75,13 +75,19 @@ 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).
78
79
  #
79
80
  # 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.
80
85
  def initialize(username_arg = nil, password_arg = nil,
81
86
  authcid: nil, username: nil,
82
87
  authzid: nil,
83
88
  password: nil, secret: nil,
84
89
  min_iterations: 4096, # see both RFC5802 and RFC7677
90
+ max_iterations: 2**31 - 1, # max int32
85
91
  cnonce: nil, # must only be set in tests
86
92
  **options)
87
93
  @username = username || username_arg || authcid or
@@ -94,7 +100,22 @@ module Net
94
100
  @min_iterations.positive? or
95
101
  raise ArgumentError, "min_iterations must be positive"
96
102
 
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
+
97
107
  @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
98
119
  end
99
120
 
100
121
  # Authentication identity: the identity that matches the #password.
@@ -127,8 +148,43 @@ module Net
127
148
 
128
149
  # The minimal allowed iteration count. Lower #iterations will raise an
129
150
  # 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.
130
163
  attr_reader :min_iterations
131
164
 
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
+
132
188
  # The client nonce, generated by SecureRandom
133
189
  attr_reader :cnonce
134
190
 
@@ -147,6 +203,15 @@ module Net
147
203
  # Net::IMAP::NoResponseError.
148
204
  attr_reader :server_error
149
205
 
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
+
150
215
  # Returns a new OpenSSL::Digest object, set to the appropriate hash
151
216
  # function for the chosen mechanism.
152
217
  #
@@ -186,6 +251,13 @@ module Net
186
251
 
187
252
  private
188
253
 
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
+
189
261
  # Need to store this for auth_message
190
262
  attr_reader :server_first_message
191
263
 
@@ -202,6 +274,8 @@ module Net
202
274
  raise Error, "server did not send iteration count"
203
275
  min_iterations <= iterations or
204
276
  raise Error, "too few iterations: %d" % [iterations]
277
+ max_iterations.nil? || iterations <= max_iterations or
278
+ raise Error, "too many iterations: %d" % [iterations]
205
279
  mext = sparams["m"] and
206
280
  raise Error, "mandatory extension: %p" % [mext]
207
281
  snonce.start_with? cnonce or
@@ -178,7 +178,7 @@ module Net
178
178
  #
179
179
  # <i>Set membership:</i>
180
180
  # - #include? (aliased as #member?):
181
- # Returns whether a given object (nz-number, range, or <tt>*</tt>) is
181
+ # Returns whether a given element (nz-number, range, or <tt>*</tt>) is
182
182
  # contained by the set.
183
183
  # - #include_star?: Returns whether the set contains <tt>*</tt>.
184
184
  #
@@ -243,13 +243,13 @@ module Net
243
243
  # These methods do not modify +self+.
244
244
  #
245
245
  # - #| (aliased as #union and #+): Returns a new set combining all members
246
- # from +self+ with all members from the other object.
246
+ # from +self+ with all members from the other set.
247
247
  # - #& (aliased as #intersection): Returns a new set containing all members
248
- # common to +self+ and the other object.
248
+ # common to +self+ and the other set.
249
249
  # - #- (aliased as #difference): Returns a copy of +self+ with all members
250
- # in the other object removed.
250
+ # in the other set removed.
251
251
  # - #^ (aliased as #xor): Returns a new set containing all members from
252
- # +self+ and the other object except those common to both.
252
+ # +self+ and the other set except those common to both.
253
253
  # - #~ (aliased as #complement): Returns a new set containing all members
254
254
  # that are not in +self+
255
255
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
@@ -262,17 +262,17 @@ module Net
262
262
  #
263
263
  # These methods always update #string to be fully sorted and coalesced.
264
264
  #
265
- # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
266
- # - #add?: If the given object is not an element in the set, adds it and
265
+ # - #add (aliased as #<<): Adds a given element to the set; returns +self+.
266
+ # - #add?: If the given element is not fully included the set, adds it and
267
267
  # returns +self+; otherwise, returns +nil+.
268
- # - #merge: Merges multiple elements into the set; returns +self+.
268
+ # - #merge: Adds all members of the given sets into this set; returns +self+.
269
269
  # - #complement!: Replaces the contents of the set with its own #complement.
270
270
  #
271
271
  # <i>Order preserving:</i>
272
272
  #
273
273
  # These methods _may_ cause #string to not be sorted or coalesced.
274
274
  #
275
- # - #append: Adds a given object to the set, appending it to the existing
275
+ # - #append: Adds the given entry to the set, appending it to the existing
276
276
  # string, and returns +self+.
277
277
  # - #string=: Assigns a new #string value and replaces #elements to match.
278
278
  # - #replace: Replaces the contents of the set with the contents
@@ -283,13 +283,14 @@ module Net
283
283
  # sorted and coalesced.
284
284
  #
285
285
  # - #clear: Removes all elements in the set; returns +self+.
286
- # - #delete: Removes a given object from the set; returns +self+.
287
- # - #delete?: If the given object is an element in the set, removes it and
286
+ # - #delete: Removes a given element from the set; returns +self+.
287
+ # - #delete?: If the given element is included in the set, removes it and
288
288
  # returns it; otherwise, returns +nil+.
289
289
  # - #delete_at: Removes the number at a given offset.
290
290
  # - #slice!: Removes the number or consecutive numbers at a given offset or
291
291
  # range of offsets.
292
- # - #subtract: Removes each given object from the set; returns +self+.
292
+ # - #subtract: Removes all members of the given sets from this set; returns
293
+ # +self+.
293
294
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
294
295
  # members over that maximum; returns +self+.
295
296
  #
@@ -326,9 +327,12 @@ module Net
326
327
  class << self
327
328
 
328
329
  # :call-seq:
329
- # SequenceSet[*values] -> valid frozen sequence set
330
+ # SequenceSet[*inputs] -> valid frozen sequence set
330
331
  #
331
- # Returns a frozen SequenceSet, constructed from +values+.
332
+ # Returns a frozen SequenceSet, constructed from +inputs+.
333
+ #
334
+ # When only a single valid frozen SequenceSet is given, that same set is
335
+ # returned.
332
336
  #
333
337
  # An empty SequenceSet is invalid and will raise a DataFormatError.
334
338
  #
@@ -672,7 +676,7 @@ module Net
672
676
  #
673
677
  # <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
674
678
  # (seqset & other))</tt>.
675
- def ^(other) remain_frozen (self | other).subtract(self & other) end
679
+ def ^(other) remain_frozen (dup | other).subtract(self & other) end
676
680
  alias xor :^
677
681
 
678
682
  # :call-seq:
@@ -694,7 +698,7 @@ module Net
694
698
  alias complement :~
695
699
 
696
700
  # :call-seq:
697
- # add(object) -> self
701
+ # add(element) -> self
698
702
  # self << other -> self
699
703
  #
700
704
  # Adds a range or number to the set and returns +self+.
@@ -702,8 +706,8 @@ module Net
702
706
  # #string will be regenerated. Use #merge to add many elements at once.
703
707
  #
704
708
  # Related: #add?, #merge, #union
705
- def add(object)
706
- tuple_add input_to_tuple object
709
+ def add(element)
710
+ tuple_add input_to_tuple element
707
711
  normalize!
708
712
  end
709
713
  alias << add
@@ -712,9 +716,9 @@ module Net
712
716
  #
713
717
  # Unlike #add, #merge, or #union, the new value is appended to #string.
714
718
  # This may result in a #string which has duplicates or is out-of-order.
715
- def append(object)
719
+ def append(entry)
716
720
  modifying!
717
- tuple = input_to_tuple object
721
+ tuple = input_to_tuple entry
718
722
  entry = tuple_to_str tuple
719
723
  string unless empty? # write @string before tuple_add
720
724
  tuple_add tuple
@@ -722,19 +726,19 @@ module Net
722
726
  self
723
727
  end
724
728
 
725
- # :call-seq: add?(object) -> self or nil
729
+ # :call-seq: add?(element) -> self or nil
726
730
  #
727
731
  # Adds a range or number to the set and returns +self+. Returns +nil+
728
- # when the object is already included in the set.
732
+ # when the element is already included in the set.
729
733
  #
730
734
  # #string will be regenerated. Use #merge to add many elements at once.
731
735
  #
732
736
  # Related: #add, #merge, #union, #include?
733
- def add?(object)
734
- add object unless include? object
737
+ def add?(element)
738
+ add element unless include? element
735
739
  end
736
740
 
737
- # :call-seq: delete(object) -> self
741
+ # :call-seq: delete(element) -> self
738
742
  #
739
743
  # Deletes the given range or number from the set and returns +self+.
740
744
  #
@@ -742,8 +746,8 @@ module Net
742
746
  # many elements at once.
743
747
  #
744
748
  # Related: #delete?, #delete_at, #subtract, #difference
745
- def delete(object)
746
- tuple_subtract input_to_tuple object
749
+ def delete(element)
750
+ tuple_subtract input_to_tuple element
747
751
  normalize!
748
752
  end
749
753
 
@@ -779,8 +783,8 @@ module Net
779
783
  # #string will be regenerated after deletion.
780
784
  #
781
785
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
782
- def delete?(object)
783
- tuple = input_to_tuple object
786
+ def delete?(element)
787
+ tuple = input_to_tuple element
784
788
  if tuple.first == tuple.last
785
789
  return unless include_tuple? tuple
786
790
  tuple_subtract tuple
@@ -824,33 +828,31 @@ module Net
824
828
  deleted
825
829
  end
826
830
 
827
- # Merges all of the elements that appear in any of the +inputs+ into the
831
+ # Merges all of the elements that appear in any of the +sets+ into the
828
832
  # set, and returns +self+.
829
833
  #
830
- # The +inputs+ may be any objects that would be accepted by ::new:
831
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
832
- # formatted strings, other sequence sets, or enumerables containing any of
833
- # these.
834
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
835
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
836
+ # strings, other sequence sets, or enumerables containing any of these.
834
837
  #
835
- # #string will be regenerated after all inputs have been merged.
838
+ # #string will be regenerated after all sets have been merged.
836
839
  #
837
840
  # Related: #add, #add?, #union
838
- def merge(*inputs)
839
- tuples_add input_to_tuples inputs
841
+ def merge(*sets)
842
+ tuples_add input_to_tuples sets
840
843
  normalize!
841
844
  end
842
845
 
843
- # Removes all of the elements that appear in any of the given +objects+
844
- # from the set, and returns +self+.
846
+ # Removes all of the elements that appear in any of the given +sets+ from
847
+ # the set, and returns +self+.
845
848
  #
846
- # The +objects+ may be any objects that would be accepted by ::new:
847
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
848
- # formatted strings, other sequence sets, or enumerables containing any of
849
- # these.
849
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
850
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
851
+ # strings, other sequence sets, or enumerables containing any of these.
850
852
  #
851
853
  # Related: #difference
852
- def subtract(*objects)
853
- tuples_subtract input_to_tuples objects
854
+ def subtract(*sets)
855
+ tuples_subtract input_to_tuples sets
854
856
  normalize!
855
857
  end
856
858
 
@@ -1243,14 +1245,18 @@ module Net
1243
1245
  def slice_range(range)
1244
1246
  first = range.begin || 0
1245
1247
  last = range.end || -1
1246
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1248
+ if range.exclude_end?
1249
+ return remain_frozen_empty if last.zero?
1250
+ last -= 1 if range.end && last != STAR_INT
1251
+ end
1247
1252
  if (first * last).positive? && last < first
1248
- SequenceSet.empty
1253
+ remain_frozen_empty
1249
1254
  elsif (min = at(first))
1250
1255
  max = at(last)
1256
+ max = :* if max.nil?
1251
1257
  if max == :* then self & (min..)
1252
1258
  elsif min <= max then self & (min..max)
1253
- else SequenceSet.empty
1259
+ else remain_frozen_empty
1254
1260
  end
1255
1261
  end
1256
1262
  end
@@ -1378,6 +1384,7 @@ module Net
1378
1384
  private
1379
1385
 
1380
1386
  def remain_frozen(set) frozen? ? set.freeze : set end
1387
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1381
1388
 
1382
1389
  # frozen clones are shallow copied
1383
1390
  def initialize_clone(other)
@@ -1390,29 +1397,29 @@ module Net
1390
1397
  super
1391
1398
  end
1392
1399
 
1393
- def input_to_tuple(obj)
1394
- obj = input_try_convert obj
1395
- case obj
1396
- when *STARS, Integer then [int = to_tuple_int(obj), int]
1397
- when Range then range_to_tuple(obj)
1398
- when String then str_to_tuple(obj)
1400
+ def input_to_tuple(entry)
1401
+ entry = input_try_convert entry
1402
+ case entry
1403
+ when *STARS, Integer then [int = to_tuple_int(entry), int]
1404
+ when Range then range_to_tuple(entry)
1405
+ when String then str_to_tuple(entry)
1399
1406
  else
1400
- raise DataFormatError, "expected number or range, got %p" % [obj]
1407
+ raise DataFormatError, "expected number or range, got %p" % [entry]
1401
1408
  end
1402
1409
  end
1403
1410
 
1404
- def input_to_tuples(obj)
1405
- obj = input_try_convert obj
1406
- case obj
1407
- when *STARS, Integer, Range then [input_to_tuple(obj)]
1408
- when String then str_to_tuples obj
1409
- when SequenceSet then obj.tuples
1410
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
1411
+ def input_to_tuples(set)
1412
+ set = input_try_convert set
1413
+ case set
1414
+ when *STARS, Integer, Range then [input_to_tuple(set)]
1415
+ when String then str_to_tuples set
1416
+ when SequenceSet then set.tuples
1417
+ when ENUMABLE then set.flat_map { input_to_tuples _1 }
1411
1418
  when nil then []
1412
1419
  else
1413
1420
  raise DataFormatError,
1414
1421
  "expected nz-number, range, string, or enumerable; " \
1415
- "got %p" % [obj]
1422
+ "got %p" % [set]
1416
1423
  end
1417
1424
  end
1418
1425