net-imap 0.4.12 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,12 +11,15 @@ module Net
11
11
  include ParserUtils
12
12
  extend ParserUtils::Generator
13
13
 
14
+ attr_reader :config
15
+
14
16
  # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser
15
- def initialize
17
+ def initialize(config: Config.global)
16
18
  @str = nil
17
19
  @pos = nil
18
20
  @lex_state = nil
19
21
  @token = nil
22
+ @config = Config[config]
20
23
  end
21
24
 
22
25
  # :call-seq:
@@ -1314,30 +1317,19 @@ module Net
1314
1317
  # header-fld-name = astring
1315
1318
  #
1316
1319
  # NOTE: Previously, Net::IMAP recreated the raw original source string.
1317
- # Now, it grabs the raw encoded value using @str and @pos. A future
1318
- # version may simply return the decoded astring value. Although that is
1319
- # technically incompatible, it should almost never make a difference: all
1320
- # standard header field names are valid atoms:
1320
+ # Now, it returns the decoded astring value. Although this is technically
1321
+ # incompatible, it should almost never make a difference: all standard
1322
+ # header field names are valid atoms:
1321
1323
  #
1322
1324
  # https://www.iana.org/assignments/message-headers/message-headers.xhtml
1323
1325
  #
1324
- # Although RFC3501 allows any astring, RFC5322-valid header names are one
1325
- # or more of the printable US-ASCII characters, except SP and colon. So
1326
- # empty string isn't valid, and literals aren't needed and should not be
1327
- # used. This is explicitly unchanged by [I18N-HDRS] (RFC6532).
1328
- #
1329
- # RFC5233:
1326
+ # See also RFC5233:
1330
1327
  # optional-field = field-name ":" unstructured CRLF
1331
1328
  # field-name = 1*ftext
1332
1329
  # ftext = %d33-57 / ; Printable US-ASCII
1333
1330
  # %d59-126 ; characters not including
1334
1331
  # ; ":".
1335
- def header_fld_name
1336
- assert_no_lookahead
1337
- start = @pos
1338
- astring
1339
- @str[start...@pos - 1]
1340
- end
1332
+ alias header_fld_name astring
1341
1333
 
1342
1334
  # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
1343
1335
  # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) /
@@ -4,44 +4,76 @@ module Net
4
4
  class IMAP
5
5
  module SASL
6
6
 
7
- # This API is *experimental*, and may change.
7
+ # AuthenticationExchange is used internally by Net::IMAP#authenticate.
8
+ # But the API is still *experimental*, and may change.
8
9
  #
9
10
  # TODO: catch exceptions in #process and send #cancel_response.
10
11
  # TODO: raise an error if the command succeeds after being canceled.
11
12
  # TODO: use with more clients, to verify the API can accommodate them.
13
+ # TODO: pass ClientAdapter#service to SASL.authenticator
12
14
  #
13
- # Create an AuthenticationExchange from a client adapter and a mechanism
14
- # authenticator:
15
- # def authenticate(mechanism, ...)
16
- # authenticator = SASL.authenticator(mechanism, ...)
17
- # SASL::AuthenticationExchange.new(
18
- # sasl_adapter, mechanism, authenticator
19
- # ).authenticate
20
- # end
21
- #
22
- # private
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.
23
21
  #
24
- # def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
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.
25
26
  #
26
- # Or delegate creation of the authenticator to ::build:
27
27
  # def authenticate(...)
28
- # SASL::AuthenticationExchange.build(sasl_adapter, ...)
29
- # .authenticate
28
+ # MyClient::SASLAdapter.new(self).authenticate(...)
30
29
  # end
31
30
  #
32
- # As a convenience, ::authenticate combines ::build and #authenticate:
31
+ # SASL::ClientAdapter#authenticate delegates to ::authenticate, like so:
32
+ #
33
33
  # def authenticate(...)
34
+ # sasl_adapter = MyClient::SASLAdapter.new(self)
34
35
  # SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
35
36
  # end
36
37
  #
37
- # Likewise, ClientAdapter#authenticate delegates to #authenticate:
38
- # def authenticate(...) = sasl_adapter.authenticate(...)
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
39
56
  #
40
57
  class AuthenticationExchange
41
58
  # Convenience method for <tt>build(...).authenticate</tt>
59
+ #
60
+ # See also: SASL::ClientAdapter#authenticate
42
61
  def self.authenticate(...) build(...).authenticate end
43
62
 
44
- # Use +registry+ to override the global Authenticators registry.
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.
45
77
  def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
46
78
  authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
47
79
  new(client, mechanism, authenticator, sasl_ir: sasl_ir)
@@ -51,7 +83,7 @@ module Net
51
83
 
52
84
  def initialize(client, mechanism, authenticator, sasl_ir: true)
53
85
  @client = client
54
- @mechanism = -mechanism.to_s.upcase.tr(?_, ?-)
86
+ @mechanism = Authenticators.normalize_name(mechanism)
55
87
  @authenticator = authenticator
56
88
  @sasl_ir = sasl_ir
57
89
  @processed = false
@@ -21,6 +21,10 @@ module Net::IMAP::SASL
21
21
  # ScramSHA1Authenticator for examples.
22
22
  class Authenticators
23
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
+
24
28
  # Create a new Authenticators registry.
25
29
  #
26
30
  # This class is usually not instantiated directly. Use SASL.authenticators
@@ -65,7 +69,6 @@ module Net::IMAP::SASL
65
69
  # lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
66
70
  # preserved and non-alphanumeric characters are removed..
67
71
  def add_authenticator(name, authenticator = nil)
68
- key = -name.to_s.upcase.tr(?_, ?-)
69
72
  authenticator ||= begin
70
73
  class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
71
74
  auth_class = nil
@@ -74,17 +77,18 @@ module Net::IMAP::SASL
74
77
  auth_class.new(*creds, **props, &block)
75
78
  }
76
79
  end
80
+ key = Authenticators.normalize_name(name)
77
81
  @authenticators[key] = authenticator
78
82
  end
79
83
 
80
84
  # Removes the authenticator registered for +name+
81
85
  def remove_authenticator(name)
82
- key = -name.to_s.upcase.tr(?_, ?-)
86
+ key = Authenticators.normalize_name(name)
83
87
  @authenticators.delete(key)
84
88
  end
85
89
 
86
90
  def mechanism?(name)
87
- key = -name.to_s.upcase.tr(?_, ?-)
91
+ key = Authenticators.normalize_name(name)
88
92
  @authenticators.key?(key)
89
93
  end
90
94
 
@@ -105,7 +109,7 @@ module Net::IMAP::SASL
105
109
  # only. Protocol client users should see refer to their client's
106
110
  # documentation, e.g. Net::IMAP#authenticate.
107
111
  def authenticator(mechanism, ...)
108
- key = -mechanism.to_s.upcase.tr(?_, ?-)
112
+ key = Authenticators.normalize_name(mechanism)
109
113
  auth = @authenticators.fetch(key) do
110
114
  raise ArgumentError, 'unknown auth type - "%s"' % key
111
115
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Net
4
6
  class IMAP
5
7
  module SASL
@@ -8,42 +10,76 @@ module Net
8
10
  #
9
11
  # TODO: use with more clients, to verify the API can accommodate them.
10
12
  #
11
- # An abstract base class for implementing a SASL authentication exchange.
12
- # Different clients will each have their own adapter subclass, overridden
13
- # to match their needs.
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.
14
17
  #
15
- # Although the default implementations _may_ be sufficient, subclasses
16
- # will probably need to override some methods. Additionally, subclasses
17
- # may need to include a protocol adapter mixin, if the default
18
+ # Subclasses should also include a protocol adapter mixin when the default
18
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.
19
27
  class ClientAdapter
28
+ extend Forwardable
29
+
20
30
  include ProtocolAdapters::Generic
21
31
 
22
- attr_reader :client, :command_proc
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
23
36
 
24
37
  # +command_proc+ can used to avoid exposing private methods on #client.
25
- # It should run a command with the arguments sent to it, yield each
26
- # continuation payload, respond to the server with the result of each
27
- # yield, and return the result. Non-successful results *MUST* raise an
28
- # exception. Exceptions in the block *MUST* cause the command to fail.
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.
29
42
  #
30
- # Subclasses that override #run_command may use #command_proc for
31
- # other purposes.
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.
32
56
  def initialize(client, &command_proc)
33
57
  @client, @command_proc = client, command_proc
34
58
  end
35
59
 
36
- # Delegates to AuthenticationExchange.authenticate.
60
+ # Attempt to authenticate #client to the server.
61
+ #
62
+ # By default, this simply delegates to
63
+ # AuthenticationExchange.authenticate.
37
64
  def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
38
65
 
39
- # Do the protocol and server both support an initial response?
40
- def sasl_ir_capable?; client.sasl_ir_capable? end
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?
41
70
 
42
- # Does the server advertise support for the mechanism?
43
- def auth_capable?(mechanism); client.auth_capable?(mechanism) end
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?
44
77
 
45
- # Runs the authenticate command with +mechanism+ and +initial_response+.
46
- # When +initial_response+ is nil, an initial response must NOT be sent.
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.
47
83
  #
48
84
  # Yields each continuation payload, responds to the server with the
49
85
  # result of each yield, and returns the result. Non-successful results
@@ -51,21 +87,36 @@ module Net
51
87
  # command to fail.
52
88
  #
53
89
  # Subclasses that override this may use #command_proc differently.
54
- def run_command(mechanism, initial_response = nil, &block)
90
+ def run_command(mechanism, initial_response = nil, &continuations_handler)
55
91
  command_proc or raise Error, "initialize with block or override"
56
92
  args = [command_name, mechanism, initial_response].compact
57
- command_proc.call(*args, &block)
93
+ command_proc.call(*args, &continuations_handler)
58
94
  end
59
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
+
60
106
  # Returns an array of server responses errors raised by run_command.
61
107
  # Exceptions in this array won't drop the connection.
62
108
  def response_errors; [] end
63
109
 
64
- # Drop the connection gracefully.
65
- def drop_connection; client.drop_connection end
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!
66
119
 
67
- # Drop the connection abruptly.
68
- def drop_connection!; client.drop_connection! end
69
120
  end
70
121
  end
71
122
  end
@@ -20,7 +20,7 @@ class Net::IMAP::SASL::CramMD5Authenticator
20
20
  warn_deprecation: true,
21
21
  **)
22
22
  if warn_deprecation
23
- warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
23
+ warn "WARNING: CRAM-MD5 mechanism is deprecated.", category: :deprecated
24
24
  end
25
25
  require "digest/md5"
26
26
  @user = authcid || username || user