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,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gs2_header"
4
+
5
+ module Net
6
+ class IMAP < Protocol
7
+ module SASL
8
+
9
+ # Abstract base class for the SASL mechanisms defined in
10
+ # RFC7628[https://www.rfc-editor.org/rfc/rfc7628]:
11
+ # * OAUTHBEARER[rdoc-ref:OAuthBearerAuthenticator]
12
+ # (OAuthBearerAuthenticator)
13
+ # * OAUTH10A
14
+ class OAuthAuthenticator
15
+ include GS2Header
16
+
17
+ # Authorization identity: an identity to act as or on behalf of. The
18
+ # identity form is application protocol specific. If not provided or
19
+ # left blank, the server derives an authorization identity from the
20
+ # authentication identity. The server is responsible for verifying the
21
+ # client's credentials and verifying that the identity it associates
22
+ # with the client's authentication identity is allowed to act as (or on
23
+ # behalf of) the authorization identity.
24
+ #
25
+ # For example, an administrator or superuser might take on another role:
26
+ #
27
+ # imap.authenticate "PLAIN", "root", passwd, authzid: "user"
28
+ #
29
+ attr_reader :authzid
30
+ alias username authzid
31
+
32
+ # Hostname to which the client connected. (optional)
33
+ attr_reader :host
34
+
35
+ # Service port to which the client connected. (optional)
36
+ attr_reader :port
37
+
38
+ # HTTP method. (optional)
39
+ attr_reader :mthd
40
+
41
+ # HTTP path data. (optional)
42
+ attr_reader :path
43
+
44
+ # HTTP post data. (optional)
45
+ attr_reader :post
46
+
47
+ # The query string. (optional)
48
+ attr_reader :qs
49
+ alias query qs
50
+
51
+ # Stores the most recent server "challenge". When authentication fails,
52
+ # this may hold information about the failure reason, as JSON.
53
+ attr_reader :last_server_response
54
+
55
+ # Creates an RFC7628[https://www.rfc-editor.org/rfc/rfc7628] OAuth
56
+ # authenticator.
57
+ #
58
+ # ==== Parameters
59
+ #
60
+ # See child classes for required parameter(s). The following parameters
61
+ # are all optional, but it is worth noting that <b>application protocols
62
+ # are allowed to require</b> #authzid (or other parameters, such as
63
+ # #host or #port) <b>as are specific server implementations</b>.
64
+ #
65
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
66
+ #
67
+ # _optional_ #username — An alias for #authzid.
68
+ #
69
+ # Note that, unlike some other authenticators, +username+ sets the
70
+ # _authorization_ identity and not the _authentication_ identity. The
71
+ # authentication identity is established for the client by the OAuth
72
+ # token.
73
+ #
74
+ # * _optional_ #host — Hostname to which the client connected.
75
+ # * _optional_ #port — Service port to which the client connected.
76
+ # * _optional_ #mthd — HTTP method
77
+ # * _optional_ #path — HTTP path data
78
+ # * _optional_ #post — HTTP post data
79
+ # * _optional_ #qs — HTTP query string
80
+ #
81
+ # _optional_ #query — An alias for #qs
82
+ #
83
+ # Any other keyword parameters are quietly ignored.
84
+ def initialize(authzid: nil, host: nil, port: nil,
85
+ username: nil, query: nil,
86
+ mthd: nil, path: nil, post: nil, qs: nil, **)
87
+ @authzid = authzid || username
88
+ @host = host
89
+ @port = port
90
+ @mthd = mthd
91
+ @path = path
92
+ @post = post
93
+ @qs = qs || query
94
+ @done = false
95
+ end
96
+
97
+ # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1]
98
+ # formatted response.
99
+ def initial_client_response
100
+ kv_pairs = {
101
+ host: host, port: port, mthd: mthd, path: path, post: post, qs: qs,
102
+ auth: authorization, # authorization is implemented by subclasses
103
+ }.compact
104
+ [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1")
105
+ end
106
+
107
+ # Returns initial_client_response the first time, then "<tt>^A</tt>".
108
+ def process(data)
109
+ @last_server_response = data
110
+ done? ? "\1" : initial_client_response
111
+ ensure
112
+ @done = true
113
+ end
114
+
115
+ # Returns true when the initial client response was sent.
116
+ #
117
+ # The authentication should not succeed unless this returns true, but it
118
+ # does *not* indicate success.
119
+ def done?; @done end
120
+
121
+ # Value of the HTTP Authorization header
122
+ #
123
+ # <b>Implemented by subclasses.</b>
124
+ def authorization; raise "must be implemented by subclass" end
125
+
126
+ end
127
+
128
+ # Authenticator for the "+OAUTHBEARER+" SASL mechanism, specified in
129
+ # RFC7628[https://www.rfc-editor.org/rfc/rfc7628]. Authenticates using
130
+ # OAuth 2.0 bearer tokens, as described in
131
+ # RFC6750[https://www.rfc-editor.org/rfc/rfc6750]. Use via
132
+ # Net::IMAP#authenticate.
133
+ #
134
+ # RFC6750[https://www.rfc-editor.org/rfc/rfc6750] requires Transport Layer
135
+ # Security (TLS) to secure the protocol interaction between the client and
136
+ # the resource server. TLS _MUST_ be used for +OAUTHBEARER+ to protect
137
+ # the bearer token.
138
+ class OAuthBearerAuthenticator < OAuthAuthenticator
139
+
140
+ # An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750]
141
+ attr_reader :oauth2_token
142
+ alias secret oauth2_token
143
+
144
+ # :call-seq:
145
+ # new(oauth2_token, **options) -> authenticator
146
+ # new(authzid, oauth2_token, **options) -> authenticator
147
+ # new(oauth2_token:, **options) -> authenticator
148
+ #
149
+ # Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism.
150
+ #
151
+ # Called by Net::IMAP#authenticate and similar methods on other clients.
152
+ #
153
+ # ==== Parameters
154
+ #
155
+ # * #oauth2_token — An OAuth2 bearer token
156
+ #
157
+ # All other keyword parameters are passed to
158
+ # {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator).
159
+ # The most common ones are:
160
+ #
161
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
162
+ #
163
+ # _optional_ #username — An alias for #authzid.
164
+ #
165
+ # Note that, unlike some other authenticators, +username+ sets the
166
+ # _authorization_ identity and not the _authentication_ identity. The
167
+ # authentication identity is established for the client by
168
+ # #oauth2_token.
169
+ #
170
+ # * _optional_ #host — Hostname to which the client connected.
171
+ # * _optional_ #port — Service port to which the client connected.
172
+ #
173
+ # Although only oauth2_token is required by this mechanism, it is worth
174
+ # noting that <b><em>application protocols are allowed to
175
+ # require</em></b> #authzid (<em>or other parameters, such as</em> #host
176
+ # _or_ #port) <b><em>as are specific server implementations</em></b>.
177
+ def initialize(arg1 = nil, arg2 = nil,
178
+ oauth2_token: nil, secret: nil,
179
+ **args, &blk)
180
+ username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2]
181
+ super(username: username, **args, &blk)
182
+ @oauth2_token = oauth2_token || secret || oauth2_token_arg or
183
+ raise ArgumentError, "missing oauth2_token"
184
+ end
185
+
186
+ # :call-seq:
187
+ # initial_response? -> true
188
+ #
189
+ # +OAUTHBEARER+ sends an initial client response.
190
+ def initial_response?; true end
191
+
192
+ # Value of the HTTP Authorization header
193
+ def authorization; "Bearer #{oauth2_token}" end
194
+
195
+ end
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Authenticator for the "+PLAIN+" SASL mechanism, specified in
4
+ # RFC-4616[https://www.rfc-editor.org/rfc/rfc4616]. See Net::IMAP#authenticate.
5
+ #
6
+ # +PLAIN+ authentication sends the password in cleartext.
7
+ # RFC-3501[https://www.rfc-editor.org/rfc/rfc3501] encourages servers to disable
8
+ # cleartext authentication until after TLS has been negotiated.
9
+ # RFC-8314[https://www.rfc-editor.org/rfc/rfc8314] recommends TLS version 1.2 or
10
+ # greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+
11
+ # can be secured by TLS encryption.
12
+ class Net::IMAP::SASL::PlainAuthenticator
13
+
14
+ NULL = -"\0".b
15
+ private_constant :NULL
16
+
17
+ # Authentication identity: the identity that matches the #password.
18
+ #
19
+ # RFC-2831[https://www.rfc-editor.org/rfc/rfc2831] uses the term +username+.
20
+ # "Authentication identity" is the generic term used by
21
+ # RFC-4422[https://www.rfc-editor.org/rfc/rfc4422].
22
+ # RFC-4616[https://www.rfc-editor.org/rfc/rfc4616] and many later RFCs
23
+ # abbreviate this to +authcid+.
24
+ attr_reader :username
25
+ alias authcid username
26
+
27
+ # A password or passphrase that matches the #username.
28
+ attr_reader :password
29
+ alias secret password
30
+
31
+ # Authorization identity: an identity to act as or on behalf of. The identity
32
+ # form is application protocol specific. If not provided or left blank, the
33
+ # server derives an authorization identity from the authentication identity.
34
+ # The server is responsible for verifying the client's credentials and
35
+ # verifying that the identity it associates with the client's authentication
36
+ # identity is allowed to act as (or on behalf of) the authorization identity.
37
+ #
38
+ # For example, an administrator or superuser might take on another role:
39
+ #
40
+ # imap.authenticate "PLAIN", "root", passwd, authzid: "user"
41
+ #
42
+ attr_reader :authzid
43
+
44
+ # :call-seq:
45
+ # new(username, password, authzid: nil, **) -> authenticator
46
+ # new(username:, password:, authzid: nil, **) -> authenticator
47
+ # new(authcid:, password:, authzid: nil, **) -> authenticator
48
+ #
49
+ # Creates an Authenticator for the "+PLAIN+" SASL mechanism.
50
+ #
51
+ # Called by Net::IMAP#authenticate and similar methods on other clients.
52
+ #
53
+ # ==== Parameters
54
+ #
55
+ # * #authcid ― Authentication identity that is associated with #password.
56
+ #
57
+ # #username ― An alias for #authcid.
58
+ #
59
+ # * #password ― A password or passphrase associated with the #authcid.
60
+ #
61
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
62
+ #
63
+ # When +authzid+ is not set, the server should derive the authorization
64
+ # identity from the authentication identity.
65
+ #
66
+ # Any other keyword parameters are quietly ignored.
67
+ def initialize(user = nil, pass = nil,
68
+ authcid: nil, secret: nil,
69
+ username: nil, password: nil, authzid: nil, **)
70
+ username ||= authcid || user or
71
+ raise ArgumentError, "missing username (authcid)"
72
+ password ||= secret || pass or raise ArgumentError, "missing password"
73
+ raise ArgumentError, "username contains NULL" if username.include?(NULL)
74
+ raise ArgumentError, "password contains NULL" if password.include?(NULL)
75
+ raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
76
+ @username = username
77
+ @password = password
78
+ @authzid = authzid
79
+ @done = false
80
+ end
81
+
82
+ # :call-seq:
83
+ # initial_response? -> true
84
+ #
85
+ # +PLAIN+ can send an initial client response.
86
+ def initial_response?; true end
87
+
88
+ # Responds with the client's credentials.
89
+ def process(data)
90
+ return "#@authzid\0#@username\0#@password"
91
+ ensure
92
+ @done = true
93
+ end
94
+
95
+ # Returns true when the initial client response was sent.
96
+ #
97
+ # The authentication should not succeed unless this returns true, but it
98
+ # does *not* indicate success.
99
+ def done?; @done end
100
+
101
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ module SASL
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.
37
+ module ProtocolAdapters
38
+ # See SASL::ProtocolAdapters@Interface.
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>.
44
+ def command_name; "AUTHENTICATE" 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.
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+.
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+.
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>.
73
+ def cancel_response; "*" end
74
+ end
75
+
76
+ # See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability),
77
+ # and RFC-9051 (IMAP4rev2).
78
+ module IMAP
79
+ include Generic
80
+ def service; "imap" end
81
+ end
82
+
83
+ # See RFC-4954 (AUTH capability).
84
+ module SMTP
85
+ include Generic
86
+ def command_name; "AUTH" end
87
+ def service; "smtp" end
88
+ end
89
+
90
+ # See RFC-5034 (SASL capability).
91
+ module POP
92
+ include Generic
93
+ def command_name; "AUTH" end
94
+ def service; "pop" end
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ module SASL
6
+
7
+ # For method descriptions,
8
+ # see {RFC5802 §2.2}[https://www.rfc-editor.org/rfc/rfc5802#section-2.2]
9
+ # and {RFC5802 §3}[https://www.rfc-editor.org/rfc/rfc5802#section-3].
10
+ module ScramAlgorithm
11
+ def Normalize(str) SASL.saslprep(str) end
12
+
13
+ def Hi(str, salt, iterations)
14
+ length = digest.digest_length
15
+ OpenSSL::KDF.pbkdf2_hmac(
16
+ str,
17
+ salt: salt,
18
+ iterations: iterations,
19
+ length: length,
20
+ hash: digest,
21
+ )
22
+ end
23
+
24
+ def H(str) digest.digest str end
25
+
26
+ def HMAC(key, data) OpenSSL::HMAC.digest(digest, key, data) end
27
+
28
+ def XOR(str1, str2)
29
+ str1.unpack("C*")
30
+ .zip(str2.unpack("C*"))
31
+ .map {|a, b| a ^ b }
32
+ .pack("C*")
33
+ end
34
+
35
+ def auth_message
36
+ [
37
+ client_first_message_bare,
38
+ server_first_message,
39
+ client_final_message_without_proof,
40
+ ]
41
+ .join(",")
42
+ end
43
+
44
+ def salted_password
45
+ Hi(Normalize(password), salt, iterations)
46
+ end
47
+
48
+ def client_key; HMAC(salted_password, "Client Key") end
49
+ def server_key; HMAC(salted_password, "Server Key") end
50
+ def stored_key; H(client_key) end
51
+ def client_signature; HMAC(stored_key, auth_message) end
52
+ def server_signature; HMAC(server_key, auth_message) end
53
+ def client_proof; XOR(client_key, client_signature) end
54
+ end
55
+
56
+ end
57
+ end
58
+ end