net-imap 0.4.0 → 0.4.1
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.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
- data/lib/net/imap/sasl/authenticators.rb +10 -4
- data/lib/net/imap/sasl/client_adapter.rb +72 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
- data/lib/net/imap/sasl.rb +8 -2
- data/lib/net/imap/sasl_adapter.rb +21 -0
- data/lib/net/imap.rb +14 -7
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bba73e8db611b37b7c5b9be540d99c2288e9188e7f12620259862b30db47815
|
4
|
+
data.tar.gz: fde1bcda99236beafea397d36768fb9fe185ec20ca2cd5d9c991b48cfdf51bd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ee43fab9eaea8c870940e0cc625e3223722e4b722e1055da6f3629707aaf12e580143139abb97ae1294757a99db977428c8aa23bc57d8e9a23ff9ab5eb695cf
|
7
|
+
data.tar.gz: 67a965fdd6b4af0480917241a8efad57c3356c82ec975bcc10373242e477718a1751e78cc9f0e51286b553e6222c26cddca119dbaa9dfef855c9611de6a55a64
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# This API is *experimental*, and may change.
|
8
|
+
#
|
9
|
+
# TODO: catch exceptions in #process and send #cancel_response.
|
10
|
+
# TODO: raise an error if the command succeeds after being canceled.
|
11
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
12
|
+
#
|
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
|
23
|
+
#
|
24
|
+
# def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
|
25
|
+
#
|
26
|
+
# Or delegate creation of the authenticator to ::build:
|
27
|
+
# def authenticate(...)
|
28
|
+
# SASL::AuthenticationExchange.build(sasl_adapter, ...)
|
29
|
+
# .authenticate
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# As a convenience, ::authenticate combines ::build and #authenticate:
|
33
|
+
# def authenticate(...)
|
34
|
+
# SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Likewise, ClientAdapter#authenticate delegates to #authenticate:
|
38
|
+
# def authenticate(...) = sasl_adapter.authenticate(...)
|
39
|
+
#
|
40
|
+
class AuthenticationExchange
|
41
|
+
# Convenience method for <tt>build(...).authenticate</tt>
|
42
|
+
def self.authenticate(...) build(...).authenticate end
|
43
|
+
|
44
|
+
# Use +registry+ to override the global Authenticators registry.
|
45
|
+
def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
|
46
|
+
authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
|
47
|
+
new(client, mechanism, authenticator, sasl_ir: sasl_ir)
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :mechanism, :authenticator
|
51
|
+
|
52
|
+
def initialize(client, mechanism, authenticator, sasl_ir: true)
|
53
|
+
@client = client
|
54
|
+
@mechanism = -mechanism.to_s.upcase.tr(?_, ?-)
|
55
|
+
@authenticator = authenticator
|
56
|
+
@sasl_ir = sasl_ir
|
57
|
+
@processed = false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call #authenticate to execute an authentication exchange for #client
|
61
|
+
# using #authenticator. Authentication failures will raise an
|
62
|
+
# exception. Any exceptions other than those in RESPONSE_ERRORS will
|
63
|
+
# drop the connection.
|
64
|
+
def authenticate
|
65
|
+
client.run_command(mechanism, initial_response) { process _1 }
|
66
|
+
.tap { raise AuthenticationIncomplete, _1 unless done? }
|
67
|
+
rescue *client.response_errors
|
68
|
+
raise # but don't drop the connection
|
69
|
+
rescue
|
70
|
+
client.drop_connection
|
71
|
+
raise
|
72
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
73
|
+
client.drop_connection!
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
|
77
|
+
def send_initial_response?
|
78
|
+
@sasl_ir &&
|
79
|
+
authenticator.respond_to?(:initial_response?) &&
|
80
|
+
authenticator.initial_response? &&
|
81
|
+
client.sasl_ir_capable? &&
|
82
|
+
client.auth_capable?(mechanism)
|
83
|
+
end
|
84
|
+
|
85
|
+
def done?
|
86
|
+
authenticator.respond_to?(:done?) ? authenticator.done? : @processed
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :client
|
92
|
+
|
93
|
+
def initial_response
|
94
|
+
return unless send_initial_response?
|
95
|
+
client.encode_ir authenticator.process nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def process(challenge)
|
99
|
+
client.encode authenticator.process client.decode challenge
|
100
|
+
ensure
|
101
|
+
@processed = true
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -65,7 +65,7 @@ module Net::IMAP::SASL
|
|
65
65
|
# lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
|
66
66
|
# preserved and non-alphanumeric characters are removed..
|
67
67
|
def add_authenticator(name, authenticator = nil)
|
68
|
-
key = name.upcase.
|
68
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
69
69
|
authenticator ||= begin
|
70
70
|
class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
|
71
71
|
auth_class = nil
|
@@ -79,10 +79,15 @@ module Net::IMAP::SASL
|
|
79
79
|
|
80
80
|
# Removes the authenticator registered for +name+
|
81
81
|
def remove_authenticator(name)
|
82
|
-
key = name.upcase.
|
82
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
83
83
|
@authenticators.delete(key)
|
84
84
|
end
|
85
85
|
|
86
|
+
def mechanism?(name)
|
87
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
88
|
+
@authenticators.key?(key)
|
89
|
+
end
|
90
|
+
|
86
91
|
# :call-seq:
|
87
92
|
# authenticator(mechanism, ...) -> auth_session
|
88
93
|
#
|
@@ -100,8 +105,9 @@ module Net::IMAP::SASL
|
|
100
105
|
# only. Protocol client users should see refer to their client's
|
101
106
|
# documentation, e.g. Net::IMAP#authenticate.
|
102
107
|
def authenticator(mechanism, ...)
|
103
|
-
|
104
|
-
|
108
|
+
key = -mechanism.to_s.upcase.tr(?_, ?-)
|
109
|
+
auth = @authenticators.fetch(key) do
|
110
|
+
raise ArgumentError, 'unknown auth type - "%s"' % key
|
105
111
|
end
|
106
112
|
auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
|
107
113
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# This API is *experimental*, and may change.
|
8
|
+
#
|
9
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
10
|
+
#
|
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.
|
14
|
+
#
|
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
|
+
# ProtocolAdapters::Generic isn't sufficient.
|
19
|
+
class ClientAdapter
|
20
|
+
include ProtocolAdapters::Generic
|
21
|
+
|
22
|
+
attr_reader :client, :command_proc
|
23
|
+
|
24
|
+
# +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.
|
29
|
+
#
|
30
|
+
# Subclasses that override #run_command may use #command_proc for
|
31
|
+
# other purposes.
|
32
|
+
def initialize(client, &command_proc)
|
33
|
+
@client, @command_proc = client, command_proc
|
34
|
+
end
|
35
|
+
|
36
|
+
# Delegates to AuthenticationExchange.authenticate.
|
37
|
+
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
|
38
|
+
|
39
|
+
# Do the protocol and server both support an initial response?
|
40
|
+
def sasl_ir_capable?; client.sasl_ir_capable? end
|
41
|
+
|
42
|
+
# Does the server advertise support for the mechanism?
|
43
|
+
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
|
44
|
+
|
45
|
+
# Runs the authenticate command with +mechanism+ and +initial_response+.
|
46
|
+
# When +initial_response+ is nil, an initial response must NOT be sent.
|
47
|
+
#
|
48
|
+
# Yields each continuation payload, responds to the server with the
|
49
|
+
# result of each yield, and returns the result. Non-successful results
|
50
|
+
# *MUST* raise an exception. Exceptions in the block *MUST* cause the
|
51
|
+
# command to fail.
|
52
|
+
#
|
53
|
+
# Subclasses that override this may use #command_proc differently.
|
54
|
+
def run_command(mechanism, initial_response = nil, &block)
|
55
|
+
command_proc or raise Error, "initialize with block or override"
|
56
|
+
args = [command_name, mechanism, initial_response].compact
|
57
|
+
command_proc.call(*args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an array of server responses errors raised by run_command.
|
61
|
+
# Exceptions in this array won't drop the connection.
|
62
|
+
def response_errors; [] end
|
63
|
+
|
64
|
+
# Drop the connection gracefully.
|
65
|
+
def drop_connection; client.drop_connection end
|
66
|
+
|
67
|
+
# Drop the connection abruptly.
|
68
|
+
def drop_connection!; client.drop_connection! end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
module ProtocolAdapters
|
8
|
+
# This API is experimental, and may change.
|
9
|
+
module Generic
|
10
|
+
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
|
14
|
+
def encode_ir(string) string.empty? ? "=" : encode(string) end
|
15
|
+
def encode(string) [string].pack("m0") end
|
16
|
+
def decode(string) string.unpack1("m0") end
|
17
|
+
def cancel_response; "*" end
|
18
|
+
end
|
19
|
+
|
20
|
+
# See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability),
|
21
|
+
# and RFC-9051 (IMAP4rev2).
|
22
|
+
module IMAP
|
23
|
+
include Generic
|
24
|
+
def service; "imap" end
|
25
|
+
end
|
26
|
+
|
27
|
+
# See RFC-4954 (AUTH capability).
|
28
|
+
module SMTP
|
29
|
+
include Generic
|
30
|
+
def command_name; "AUTH" end
|
31
|
+
def service; "smtp" end
|
32
|
+
end
|
33
|
+
|
34
|
+
# See RFC-5034 (SASL capability).
|
35
|
+
module POP
|
36
|
+
include Generic
|
37
|
+
def command_name; "AUTH" end
|
38
|
+
def service; "pop" end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/net/imap/sasl.rb
CHANGED
@@ -135,6 +135,10 @@ module Net
|
|
135
135
|
autoload :BidiStringError, sasl_stringprep_rb
|
136
136
|
|
137
137
|
sasl_dir = File.expand_path("sasl", __dir__)
|
138
|
+
autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange"
|
139
|
+
autoload :ClientAdapter, "#{sasl_dir}/client_adapter"
|
140
|
+
autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters"
|
141
|
+
|
138
142
|
autoload :Authenticators, "#{sasl_dir}/authenticators"
|
139
143
|
autoload :GS2Header, "#{sasl_dir}/gs2_header"
|
140
144
|
autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm"
|
@@ -155,8 +159,10 @@ module Net
|
|
155
159
|
# Returns the default global SASL::Authenticators instance.
|
156
160
|
def self.authenticators; @authenticators ||= Authenticators.new end
|
157
161
|
|
158
|
-
# Delegates to
|
159
|
-
def self.authenticator(
|
162
|
+
# Delegates to <tt>registry.new</tt> See Authenticators#new.
|
163
|
+
def self.authenticator(*args, registry: authenticators, **kwargs, &block)
|
164
|
+
registry.new(*args, **kwargs, &block)
|
165
|
+
end
|
160
166
|
|
161
167
|
# Delegates to ::authenticators. See Authenticators#add_authenticator.
|
162
168
|
def self.add_authenticator(...) authenticators.add_authenticator(...) end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
|
6
|
+
# Experimental
|
7
|
+
class SASLAdapter < SASL::ClientAdapter
|
8
|
+
include SASL::ProtocolAdapters::IMAP
|
9
|
+
|
10
|
+
RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError]
|
11
|
+
.freeze
|
12
|
+
|
13
|
+
def response_errors; RESPONSE_ERRORS end
|
14
|
+
def sasl_ir_capable?; client.capable?("SASL-IR") end
|
15
|
+
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
|
16
|
+
def drop_connection; client.logout! end
|
17
|
+
def drop_connection!; client.disconnect end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/net/imap.rb
CHANGED
@@ -127,7 +127,7 @@ module Net
|
|
127
127
|
# end
|
128
128
|
#
|
129
129
|
# # Support for "UTF8=ACCEPT" implies support for "ENABLE"
|
130
|
-
# imap.enable :utf8 if imap.
|
130
|
+
# imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
|
131
131
|
#
|
132
132
|
# namespaces = imap.namespace if imap.capable?(:namespace)
|
133
133
|
# mbox_prefix = namespaces&.personal&.first&.prefix || ""
|
@@ -662,7 +662,7 @@ module Net
|
|
662
662
|
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
663
663
|
#
|
664
664
|
class IMAP < Protocol
|
665
|
-
VERSION = "0.4.
|
665
|
+
VERSION = "0.4.1"
|
666
666
|
|
667
667
|
# Aliases for supported capabilities, to be used with the #enable command.
|
668
668
|
ENABLE_ALIASES = {
|
@@ -670,8 +670,9 @@ module Net
|
|
670
670
|
"UTF8=ONLY" => "UTF8=ACCEPT",
|
671
671
|
}.freeze
|
672
672
|
|
673
|
-
autoload :SASL,
|
674
|
-
autoload :
|
673
|
+
autoload :SASL, File.expand_path("imap/sasl", __dir__)
|
674
|
+
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
|
675
|
+
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
|
675
676
|
|
676
677
|
include MonitorMixin
|
677
678
|
if defined?(OpenSSL::SSL)
|
@@ -1142,7 +1143,10 @@ module Net
|
|
1142
1143
|
end
|
1143
1144
|
|
1144
1145
|
# :call-seq:
|
1145
|
-
# authenticate(mechanism, *,
|
1146
|
+
# authenticate(mechanism, *,
|
1147
|
+
# sasl_ir: true,
|
1148
|
+
# registry: Net::IMAP::SASL.authenticators,
|
1149
|
+
# **, &) -> ok_resp
|
1146
1150
|
#
|
1147
1151
|
# Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
|
1148
1152
|
# to authenticate the client. If successful, the connection enters the
|
@@ -1886,8 +1890,7 @@ module Net
|
|
1886
1890
|
# +attr+ is a list of attributes to fetch; see the documentation
|
1887
1891
|
# for FetchData for a list of valid attributes.
|
1888
1892
|
#
|
1889
|
-
# The return value is an array of FetchData
|
1890
|
-
# (instead of an empty array) if there is no matching message.
|
1893
|
+
# The return value is an array of FetchData.
|
1891
1894
|
#
|
1892
1895
|
# Related: #uid_search, FetchData
|
1893
1896
|
#
|
@@ -2747,6 +2750,10 @@ module Net
|
|
2747
2750
|
end
|
2748
2751
|
end
|
2749
2752
|
|
2753
|
+
def sasl_adapter
|
2754
|
+
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
2755
|
+
end
|
2756
|
+
|
2750
2757
|
#--
|
2751
2758
|
# We could get the saslprep method by extending the SASLprep module
|
2752
2759
|
# directly. It's done indirectly, so SASLprep can be lazily autoloaded,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-10-
|
12
|
+
date: 2023-10-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-protocol
|
@@ -100,7 +100,9 @@ files:
|
|
100
100
|
- lib/net/imap/response_parser/parser_utils.rb
|
101
101
|
- lib/net/imap/sasl.rb
|
102
102
|
- lib/net/imap/sasl/anonymous_authenticator.rb
|
103
|
+
- lib/net/imap/sasl/authentication_exchange.rb
|
103
104
|
- lib/net/imap/sasl/authenticators.rb
|
105
|
+
- lib/net/imap/sasl/client_adapter.rb
|
104
106
|
- lib/net/imap/sasl/cram_md5_authenticator.rb
|
105
107
|
- lib/net/imap/sasl/digest_md5_authenticator.rb
|
106
108
|
- lib/net/imap/sasl/external_authenticator.rb
|
@@ -108,10 +110,12 @@ files:
|
|
108
110
|
- lib/net/imap/sasl/login_authenticator.rb
|
109
111
|
- lib/net/imap/sasl/oauthbearer_authenticator.rb
|
110
112
|
- lib/net/imap/sasl/plain_authenticator.rb
|
113
|
+
- lib/net/imap/sasl/protocol_adapters.rb
|
111
114
|
- lib/net/imap/sasl/scram_algorithm.rb
|
112
115
|
- lib/net/imap/sasl/scram_authenticator.rb
|
113
116
|
- lib/net/imap/sasl/stringprep.rb
|
114
117
|
- lib/net/imap/sasl/xoauth2_authenticator.rb
|
118
|
+
- lib/net/imap/sasl_adapter.rb
|
115
119
|
- lib/net/imap/stringprep.rb
|
116
120
|
- lib/net/imap/stringprep/nameprep.rb
|
117
121
|
- lib/net/imap/stringprep/saslprep.rb
|