net-sasl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 808eb424556d20a21234ed67c852cf7f664c3964b98109ff0180ab16aea19da1
4
+ data.tar.gz: 302b360c9e02f77d72d3f3daf9124444df70ee73c51d489a116b8eb87c1ab720
5
+ SHA512:
6
+ metadata.gz: c1747077cf7bbd3d16a248491b4143ef6e6e33e347a9c8c35025c803079b42077584ebf46df977103599d1d7d84b5d9c5f3a5bb0f5ab4d4e68278bc9fdc422d2
7
+ data.tar.gz: 453cc6578665f03b5f4dce9d9fcd039caee5e0f5238ec3fc499c566d5357b0ee8f5c2d5d0074e06597d81705585b5a2a65a7c8356856356f301617e6a601c6b4
@@ -0,0 +1,23 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '3.0', 2.7, 2.6, 2.5, head ]
11
+ os: [ ubuntu-latest, macos-latest ]
12
+ runs-on: ${{ matrix.os }}
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ bundler-cache: true
20
+ - name: Install dependencies
21
+ run: bundle install
22
+ - name: Run the default task (tests, rubocop, etc)
23
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,198 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.5
7
+ NewCops: disable
8
+ Exclude:
9
+ - bin/rake
10
+ - bin/rspec
11
+ - bin/rubocop
12
+
13
+ ###########################################################################
14
+ # rubocop defaults are distractingly WRONG about many rules... Sorry. :(
15
+
16
+ ###########################################################################
17
+ # Layout: Alignment. I want these to work, I really do...
18
+
19
+ # I wish this worked with "table". but that goes wrong sometimes.
20
+ Layout/HashAlignment: { Enabled: false }
21
+
22
+ # This needs to be configurable so parenthesis calls are aligned with first
23
+ # parameter, and non-parenthesis calls are aligned with fixed indentation.
24
+ Layout/ParameterAlignment: { Enabled: false }
25
+
26
+ ###########################################################################
27
+ # Layout: Empty lines
28
+
29
+ Layout/EmptyLineAfterGuardClause: { Enabled: false }
30
+ Layout/EmptyLineAfterMagicComment: { Enabled: true }
31
+ Layout/EmptyLineAfterMultilineCondition: { Enabled: false }
32
+ Layout/EmptyLines: { Enabled: true }
33
+ Layout/EmptyLinesAroundAccessModifier: { Enabled: true }
34
+ Layout/EmptyLinesAroundArguments: { Enabled: true }
35
+ Layout/EmptyLinesAroundBeginBody: { Enabled: true }
36
+ Layout/EmptyLinesAroundBlockBody: { Enabled: false }
37
+ Layout/EmptyLinesAroundExceptionHandlingKeywords: { Enabled: true }
38
+ Layout/EmptyLinesAroundMethodBody: { Enabled: true }
39
+
40
+ Layout/EmptyLineBetweenDefs:
41
+ Enabled: true
42
+ AllowAdjacentOneLineDefs: true
43
+
44
+ Layout/EmptyLinesAroundAttributeAccessor:
45
+ inherit_mode:
46
+ merge:
47
+ - Exclude
48
+ - AllowedMethods
49
+ Enabled: true
50
+ AllowedMethods:
51
+ - delegate
52
+ - def_delegator
53
+ - def_delegators
54
+ - def_instance_delegators
55
+
56
+ # "empty_lines_special" sometimes does the wrong thing and annoys me.
57
+ # I'd almost learned to live with it... almost. 🙁
58
+
59
+ Layout/EmptyLinesAroundClassBody:
60
+ Enabled: false
61
+ EnforcedStyle: empty_lines_special
62
+
63
+ Layout/EmptyLinesAroundModuleBody:
64
+ Enabled: false
65
+ EnforcedStyle: empty_lines_special
66
+
67
+ ###########################################################################
68
+ # Layout: Space around, before, inside, etc
69
+
70
+ Layout/SpaceAroundEqualsInParameterDefault: { Enabled: false }
71
+ Layout/SpaceBeforeBlockBraces: { Enabled: false }
72
+ Layout/SpaceBeforeFirstArg: { Enabled: false }
73
+ Layout/SpaceInLambdaLiteral: { Enabled: false }
74
+ Layout/SpaceInsideArrayLiteralBrackets: { Enabled: false }
75
+ Layout/SpaceInsideHashLiteralBraces: { Enabled: false }
76
+
77
+ Layout/SpaceInsideBlockBraces:
78
+ EnforcedStyle: space
79
+ EnforcedStyleForEmptyBraces: space
80
+ SpaceBeforeBlockParameters: false
81
+
82
+ # I would enable this if it were handled alignment better
83
+ Layout/ExtraSpacing:
84
+ Enabled: false
85
+ AllowForAlignment: true
86
+ AllowBeforeTrailingComments: true
87
+
88
+ ###########################################################################
89
+ # Layout: Misc
90
+
91
+ Layout/LineLength:
92
+ Max: 90 # should stay under 80, but we'll allow a little wiggle-room
93
+
94
+ Layout/MultilineOperationIndentation: { Enabled: false }
95
+
96
+ Layout/MultilineMethodCallIndentation:
97
+ EnforcedStyle: indented
98
+
99
+ ###########################################################################
100
+ # Lint and Naming: rubocop defaults are mostly good, but...
101
+
102
+ Lint/UnusedMethodArgument: { Enabled: false }
103
+ Naming/BinaryOperatorParameterName: { Enabled: false } # def /(denominator)
104
+ Naming/RescuedExceptionsVariableName: { Enabled: false }
105
+
106
+ ###########################################################################
107
+ # Matrics:
108
+
109
+ Metrics/CyclomaticComplexity:
110
+ Max: 10
111
+
112
+ Metrics/BlockLength:
113
+ CountAsOne:
114
+ - array
115
+ - hash
116
+ - heredoc
117
+
118
+ Metrics/ClassLength:
119
+ Max: 200
120
+ CountAsOne:
121
+ - array
122
+ - hash
123
+ - heredoc
124
+
125
+ ###########################################################################
126
+ # Style...
127
+
128
+ Style/AccessorGrouping: { Enabled: false }
129
+ Style/AsciiComments: { Enabled: false } # 👮 can't stop our 🎉🥳🎊🥳!
130
+ Style/ClassAndModuleChildren: { Enabled: false }
131
+ Style/EachWithObject: { Enabled: false }
132
+ Style/FormatStringToken: { Enabled: false }
133
+ Style/FloatDivision: { Enabled: false }
134
+ Style/GuardClause: { Enabled: false } # usually nice to do, but...
135
+ Style/IfUnlessModifier: { Enabled: false }
136
+ Style/IfWithSemicolon: { Enabled: false }
137
+ Style/Lambda: { Enabled: false }
138
+ Style/LineEndConcatenation: { Enabled: false }
139
+ Style/MixinGrouping: { Enabled: false }
140
+ Style/MultilineBlockChain: { Enabled: false }
141
+ Style/NumericPredicate: { Enabled: false } # usually nice to do, but...
142
+ Style/ParallelAssignment: { Enabled: false } # use occasionally/sparingly
143
+ Style/PerlBackrefs: { Enabled: false } # use occasionally/sparingly
144
+ Style/RescueStandardError: { Enabled: false }
145
+ Style/Semicolon: { Enabled: false }
146
+ Style/SingleLineMethods: { Enabled: false }
147
+ Style/StabbyLambdaParentheses: { Enabled: false }
148
+ Style/WhenThen : { Enabled: false }
149
+
150
+ # I require trailing commas elsewhere, but these are optional
151
+ Style/TrailingCommaInArguments: { Enabled: false }
152
+
153
+ # If rubocop had an option to only enforce this on constants and literals (e.g.
154
+ # strings, regexp, range), I'd agree.
155
+ #
156
+ # But if you are using it e.g. on method arguments of unknown type, in the same
157
+ # style that ruby uses it with grep, then you are doing exactly the right thing.
158
+ Style/CaseEquality: { Enabled: false }
159
+
160
+ # I'd enable if "require_parentheses_when_complex" considered unary '!' simple.
161
+ Style/TernaryParentheses:
162
+ EnforcedStyle: require_parentheses_when_complex
163
+ Enabled: false
164
+
165
+ Style/BlockDelimiters:
166
+ inherit_mode:
167
+ merge:
168
+ - Exclude
169
+ - ProceduralMethods
170
+ - IgnoredMethods
171
+ - FunctionalMethods
172
+ EnforcedStyle: semantic
173
+ AllowBracesOnProceduralOneLiners: true
174
+ IgnoredMethods:
175
+ - expect # rspec
176
+ - profile # ruby-prof
177
+ - ips # benchmark-ips
178
+
179
+
180
+ Style/FormatString:
181
+ EnforcedStyle: percent
182
+
183
+ Style/StringLiterals:
184
+ Enabled: true
185
+ EnforcedStyle: double_quotes
186
+
187
+ Style/StringLiteralsInInterpolation:
188
+ Enabled: true
189
+ EnforcedStyle: double_quotes
190
+
191
+ Style/TrailingCommaInHashLiteral:
192
+ EnforcedStyleForMultiline: consistent_comma
193
+
194
+ Style/TrailingCommaInArrayLiteral:
195
+ EnforcedStyleForMultiline: consistent_comma
196
+
197
+ Style/YodaCondition:
198
+ EnforcedStyle: forbid_for_equality_operators_only
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-04-30
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at nicholas.evans@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in net-sasl.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "test-unit", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.7"
13
+ gem "rubocop-rake"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 nicholas a. evans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Net::SASL
2
+
3
+ Pluggable mechanisms to support protocols which use SASL.
4
+
5
+ Originally written for Net::IMAP by Shugo Maeda, and extracted to this library
6
+ by Nicholas Evans.
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'net-sasl'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install net-sasl
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
32
+ `rake test-unit` to run the tests. You can also run `bin/console` for an
33
+ interactive prompt that will allow you to experiment.
34
+
35
+ To install this gem onto your local machine, run `bundle exec rake install`. To
36
+ release a new version, update the version number in `version.rb`, and then run
37
+ `bundle exec rake release`, which will create a git tag for the version, push
38
+ git commits and the created tag, and push the `.gem` file to
39
+ [rubygems.org](https://rubygems.org).
40
+
41
+ ## Contributing
42
+
43
+ Bug reports and pull requests are welcome on GitHub at
44
+ https://github.com/nevans/net-sasl. This project is intended to be a safe,
45
+ welcoming space for collaboration, and contributors are expected to adhere to
46
+ the [code of
47
+ conduct](https://github.com/nevans/net-sasl/blob/master/CODE_OF_CONDUCT.md).
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms of the [MIT
52
+ License](https://opensource.org/licenses/MIT).
53
+
54
+ ## Code of Conduct
55
+
56
+ Everyone interacting in the Net::SASL project's codebases, issue trackers, chat
57
+ rooms and mailing lists is expected to follow the [code of
58
+ conduct](https://github.com/nevans/net-sasl/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "net/sasl"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/net/sasl.rb ADDED
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sasl/version"
4
+ require_relative "sasl/registry"
5
+ require_relative "sasl/authenticator"
6
+ require_relative "sasl/cram_md5_authenticator"
7
+ require_relative "sasl/digest_md5_authenticator"
8
+ require_relative "sasl/login_authenticator"
9
+ require_relative "sasl/plain_authenticator"
10
+
11
+ module Net
12
+
13
+ # Pluggable authentication mechanisms for protocols which support SASL (Simple
14
+ # Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and XMPP.
15
+ # SASL is described by RFC4422[https://tools.ietf.org/html/rfc4422]: "SASL is
16
+ # conceptually a framework that provides an abstraction layer between
17
+ # protocols and mechanisms as illustrated in the following diagram."
18
+ #
19
+ # SMTP LDAP XMPP Other protocols ...
20
+ # \ | | /
21
+ # \ | | /
22
+ # SASL abstraction layer
23
+ # / | | \
24
+ # / | | \
25
+ # EXTERNAL GSSAPI PLAIN Other mechanisms ...
26
+ #
27
+ # This library was originally implemented for Net::IMAP, and has been
28
+ # extracted from there.
29
+ module SASL
30
+
31
+ # Superclass of SASL errors.
32
+ class Error < StandardError
33
+ end
34
+
35
+ # Error raised when data is in the incorrect format.
36
+ class DataFormatError < Error
37
+ end
38
+
39
+ # Error raised when a challnge from the server is non-parseable or the
40
+ # mechanism implementation is unable to respond
41
+ class ChallengeParseError < Error
42
+ end
43
+
44
+ # Adds an authenticator to the global registry, for use with
45
+ # Net::SASL.authenticator. See Net::SASL::Registry#add_authenticator.
46
+ def self.add_authenticator(mechanism, authenticator)
47
+ DEFAULT_REGISTRY.add_authenticator(mechanism, authenticator)
48
+ end
49
+
50
+ # Builds an authenticator in its initial state, based on +mechanism+ name.
51
+ # Any additional arguments will be passed directly to the chosen
52
+ # authenticator's +#new+ method. See Net::SASL::Registry#authenticator.
53
+ def self.authenticator(mechanism, *args, **kwargs)
54
+ DEFAULT_REGISTRY.authenticator(mechanism, *args, **kwargs)
55
+ end
56
+
57
+ # The default global registry used by Net::SASL.authenticator
58
+ DEFAULT_REGISTRY = Registry.new
59
+
60
+ add_authenticator "PLAIN", PlainAuthenticator
61
+ add_authenticator "LOGIN", LoginAuthenticator
62
+ add_authenticator "DIGEST-MD5", DigestMD5Authenticator
63
+ add_authenticator "CRAM-MD5", CramMD5Authenticator
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+
5
+ module SASL
6
+
7
+ # A base class to use for SASL authenticators.
8
+ class Authenticator
9
+
10
+ # Creates a new authenticator.
11
+ #
12
+ # Each specific mechanism determines how the arguments are interpreted—see
13
+ # each mechanisms' documentation for details. Whenever it's reasonable,
14
+ # mechanisms should support the standard positional and keyword arguments
15
+ # and ignore any irrelevant or unknown arguments.
16
+ #
17
+ # === Standard arguments
18
+ #
19
+ # * +authcid+: the authentication identity, the identity associated with
20
+ # the authentication credentials. This is usually a +username+.
21
+ # * +credentials+: the authentication credentials, e.g. a +password+ or a
22
+ # secret bearer token. Some mechanisms may not require an explicit
23
+ # +authcid+ if it is encoded inside the authentication credentials.
24
+ # * +authzid+: the authorization identity, an identity to act as or on
25
+ # behalf of. If this is is not given (or is left blank), the server
26
+ # will derive an authorization identity from the authentication
27
+ # credentials, usually the same as the authentication identity.
28
+ #
29
+ # The server is responsible for verifying the client's credentials and
30
+ # verifying that the identity it associates with the client's credentials
31
+ # (e.g., the authentication identity) is allowed to act as the
32
+ # authorization identity. The precise form(s) of identities and
33
+ # credentials may be dictated by the mechanism and by the server.
34
+ #
35
+ # === Standard options
36
+ #
37
+ # * +host+: the server hostname which is being connected to
38
+ # * +port+: the server port being connected to
39
+ # * +realm+: some mechanisms use "realms" or "domains" to segment
40
+ # authentication identities. This is protocol dependant and it might be
41
+ # the same as +host+.
42
+ #
43
+ def initialize(authcid = nil, credentials = nil, authzid = nil, **_options)
44
+ @username = authcid
45
+ @password = credentials
46
+ @authzid = authzid
47
+ end
48
+
49
+ # Does this mechanism support sending an initial response via SASL-IR?
50
+ def supports_initial_response?
51
+ false
52
+ end
53
+
54
+ # Process a +challenge+ string from the server and return the response.
55
+ # This method should be sent an unencoded challenge and return an
56
+ # unencoded response. The client is responsible for receiving and decoding
57
+ # the challenge, according the the specification of the specific protocol,
58
+ # e.g. IMAP4 base64 encodes challenges and responses.
59
+ #
60
+ # A nil +challenge+ will be sent to get the initial responses, when
61
+ # that is supported by the mechanism (#supports_initial_response? returns
62
+ # true) and by the protocol.
63
+ #
64
+ # Calling #process when #done? returns true has undefined behavior: it may
65
+ # raise an excepion, return the previous response again, or raise an
66
+ # exception.
67
+ def process(challenge)
68
+ raise NotImplementedError, "implemented by SASL mechanism subclasses"
69
+ end
70
+
71
+ # Has the authenticator finished? If so, then clients must not call
72
+ # #process again. This is so clients can know authentication is supposed
73
+ # to have been completed, without needing to call #process and handle an
74
+ # exception there.
75
+ def done?
76
+ false
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+
5
+ module Net
6
+
7
+ module SASL
8
+
9
+ # Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
10
+ # RFC2195[https://tools.ietf.org/html/rfc2195].
11
+ #
12
+ # == Deprecated
13
+ #
14
+ # +CRAM-MD5+ is obsolete. It is included for compatibility with existing
15
+ # servers.
16
+ # {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html]
17
+ # recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead.
18
+ class CramMD5Authenticator < Authenticator
19
+
20
+ attr_reader :username, :password, :done
21
+
22
+ alias done? done
23
+ private :done
24
+
25
+ # Provide the +username+ and +password+ credentials for authentication.
26
+ #
27
+ # CRAM-MD5 doesn't support +authzid+, and an ArgumentError will be raised
28
+ # if a third positional parameter is passed.
29
+ #
30
+ # This should generally be instantiated via Net::SASL.authenticator.
31
+ def initialize(username, password, **_options)
32
+ super
33
+ @username = username
34
+ @password = password
35
+ @done = false
36
+ end
37
+
38
+ # responds to the server's challenge using the HMAC-MD5 algorithm.
39
+ def process(challenge)
40
+ digest = hmac_md5(challenge, password)
41
+ "#{username} #{digest}"
42
+ end
43
+
44
+ private
45
+
46
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
47
+
48
+ def hmac_md5(text, key)
49
+ if key.length > 64
50
+ key = Digest::MD5.digest(key)
51
+ end
52
+
53
+ k_ipad = key + "\0" * (64 - key.length)
54
+ k_opad = key + "\0" * (64 - key.length)
55
+ (0..63).each do |i|
56
+ k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
57
+ k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
58
+ end
59
+
60
+ digest = Digest::MD5.digest(k_ipad + text)
61
+
62
+ Digest::MD5.hexdigest(k_opad + digest)
63
+ end
64
+
65
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+ require "strscan"
5
+
6
+ module Net
7
+
8
+ module SASL
9
+
10
+ # Authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
11
+ # in RFC2831(https://tools.ietf.org/html/rfc2831).
12
+ #
13
+ # == Deprecated
14
+ #
15
+ # "+DIGEST-MD5+" has been deprecated by
16
+ # {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on
17
+ # for security. It is included for compatibility with existing servers.
18
+ class DigestMD5Authenticator < Authenticator
19
+
20
+ STAGE_ONE = :stage_one
21
+ STAGE_TWO = :stage_two
22
+ private_constant :STAGE_ONE, :STAGE_TWO
23
+
24
+ attr_reader :username, :password, :authzid
25
+
26
+ # Provide the +username+ and +password+ credentials. An optional
27
+ # +authzid+ is defined as: "The "authorization ID" as per
28
+ # RFC2222[https://tools.ietf.org/html/rfc2222],
29
+ # encoded in UTF-8. optional. If present, and the
30
+ # authenticating user has sufficient privilege, and the server supports
31
+ # it, then after authentication the server will use this identity for
32
+ # making all accesses and access checks. If the client specifies it, and
33
+ # the server does not support it, then the response-value will be
34
+ # incorrect, and authentication will fail."
35
+ #
36
+ # This should generally be instantiated via Net::SASL.authenticator.
37
+ def initialize(username, password, authzid = nil, **_options)
38
+ super
39
+ @username, @password, @authzid = username, password, authzid
40
+ @nc, @stage = {}, STAGE_ONE
41
+ end
42
+
43
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
44
+
45
+ # responds to the server's DIGEST-MD5 challenges
46
+ def process(challenge)
47
+ case @stage
48
+ when STAGE_ONE
49
+ @stage = STAGE_TWO
50
+ sparams = {}
51
+ c = StringScanner.new(challenge)
52
+ while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
53
+ k, v = c[1], c[2]
54
+ if v =~ /^"(.*)"$/
55
+ v = $1
56
+ if v =~ /,/
57
+ v = v.split(",")
58
+ end
59
+ end
60
+ sparams[k] = v
61
+ end
62
+
63
+ raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.empty?
64
+ unless sparams["qop"].include?("auth")
65
+ raise Error,
66
+ "Server does not support auth (qop = #{sparams["qop"].join(",")})"
67
+ end
68
+
69
+ response = {
70
+ nonce: sparams["nonce"],
71
+ username: @username,
72
+ realm: sparams["realm"],
73
+ cnonce: Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand,
74
+ Process.pid.to_s,]),
75
+ 'digest-uri': "imap/#{sparams["realm"]}",
76
+ qop: "auth",
77
+ maxbuf: 65_535,
78
+ nc: "%08d" % nc(sparams["nonce"]),
79
+ charset: sparams["charset"],
80
+ }
81
+
82
+ response[:authzid] = @authzid unless @authzid.nil?
83
+
84
+ # now, the real thing
85
+ a0 = Digest::MD5.digest([ response.values_at(:username, :realm),
86
+ @password, ].join(":"))
87
+
88
+ a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":")
89
+ a1 << ":#{response[:authzid]}" unless response[:authzid].nil?
90
+
91
+ a2 = "AUTHENTICATE:#{response[:'digest-uri']}"
92
+ if response[:qop] && response[:qop] =~ (/^auth-(?:conf|int)$/)
93
+ a2 << ":00000000000000000000000000000000"
94
+ end
95
+
96
+ response[:response] = Digest::MD5.hexdigest(
97
+ [
98
+ Digest::MD5.hexdigest(a1),
99
+ response.values_at(:nonce, :nc, :cnonce, :qop),
100
+ Digest::MD5.hexdigest(a2),
101
+ ].join(":")
102
+ )
103
+
104
+ response.keys.map {|key| qdval(key.to_s, response[key]) }.join(",")
105
+ when STAGE_TWO
106
+ @stage = nil
107
+ # if at the second stage, return an empty string
108
+ if challenge =~ /rspauth=/
109
+ ""
110
+ else
111
+ raise ChallengeParseError, challenge
112
+ end
113
+ else
114
+ raise ChallengeParseError, challenge
115
+ end
116
+ end
117
+
118
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
119
+
120
+ # returns true after two challenge/response stages
121
+ def done?
122
+ @stage.nil?
123
+ end
124
+
125
+ private
126
+
127
+ def nc(nonce)
128
+ @nc[nonce] = if @nc.key? nonce
129
+ @nc[nonce] + 1
130
+ else
131
+ 1
132
+ end
133
+ @nc[nonce]
134
+ end
135
+
136
+ # some responses need quoting
137
+ def qdval(k, v) # rubocop:disable Naming/MethodParameterName
138
+ return if k.nil? || v.nil?
139
+ if %w[username authzid realm nonce cnonce digest-uri qop].include? k
140
+ v.gsub!(/([\\"])/, "\\\1")
141
+ '%s="%s"' % [k, v]
142
+ else
143
+ "%s=%s" % [k, v]
144
+ end
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+
5
+ module SASL
6
+
7
+ # Authenticator for the "+LOGIN+" SASL mechanism. The authentication
8
+ # credentials are transmitted in cleartext so this mechanism should only be
9
+ # used over an encrypted link.
10
+ #
11
+ # === Deprecated
12
+ #
13
+ # The {SASL mechanisms registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
14
+ # marks "LOGIN" as obsoleted by "PLAIN". It is included here for
15
+ # compatibility with existing servers. See
16
+ # draft-murchison-sasl-login[https://www.iana.org/go/draft-murchison-sasl-login]
17
+ # for both specification and deprecation.
18
+ class LoginAuthenticator < Authenticator
19
+
20
+ attr_reader :username, :password
21
+
22
+ # Provide the +username+ and +password+ credentials for authentication.
23
+ #
24
+ # LOGIN doesn't support +authzid+, and an ArgumentError will be raised if
25
+ # a third positional parameter is passed.
26
+ #
27
+ # This should generally be instantiated via Net::SASL.authenticator.
28
+ def initialize(username, password, **_options)
29
+ super
30
+ @state = STATE_USER
31
+ end
32
+
33
+ # returns the SASL response for +LOGIN+
34
+ def process(data)
35
+ case @state
36
+ when STATE_USER
37
+ @state = STATE_PASSWORD
38
+ @username
39
+ when STATE_PASSWORD
40
+ @state = STATE_DONE
41
+ @password
42
+ end
43
+ end
44
+
45
+ # Returns true after sending the username and password.
46
+ def done?
47
+ @state == STATE_DONE
48
+ end
49
+
50
+ STATE_USER = :USER
51
+ STATE_PASSWORD = :PASSWORD
52
+ STATE_DONE = :DONE
53
+
54
+ private_constant :STATE_USER, :STATE_PASSWORD
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+
5
+ module SASL
6
+
7
+ # Authenticator for the "+PLAIN+" SASL mechanism, specified in
8
+ # RFC4616[https://tools.ietf.org/html/rfc4616]. The authentication
9
+ # credentials are transmitted in cleartext, so this mechanism should only be
10
+ # used over an encrypted link.
11
+ class PlainAuthenticator < Authenticator
12
+
13
+ NULL = -"\0".b
14
+ private_constant :NULL
15
+
16
+ attr_reader :username, :password, :authzid, :done
17
+
18
+ alias done? done
19
+ private :done
20
+
21
+ # +username+ is the authentication identity, the identity whose +password+ is
22
+ # used. +username+ is referred to as +authcid+ by
23
+ # RFC4616[https://tools.ietf.org/html/rfc4616].
24
+ #
25
+ # +authzid+ is the authorization identity (identity to act as). It can
26
+ # usually be left blank. When +authzid+ is left blank (nil or empty string)
27
+ # the server will derive an identity from the credentials and use that as the
28
+ # authorization identity.
29
+ #
30
+ # This should generally be instantiated via Net::SASL.authenticator.
31
+ def initialize(username, password, authzid = nil, **_options)
32
+ raise ArgumentError, "username contains NULL" if username&.include?(NULL)
33
+ raise ArgumentError, "password contains NULL" if password&.include?(NULL)
34
+ raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
35
+ super
36
+ @done = false
37
+ end
38
+
39
+ # +PLAIN+ does support SASL-IR
40
+ def supports_initial_response?
41
+ true
42
+ end
43
+
44
+ # returns the SASL response for +PLAIN+
45
+ def process(data)
46
+ @done = true
47
+ "#{@authzid}\0#{@username}\0#{@password}"
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+
5
+ module SASL
6
+
7
+ # Registry for SASL mechanisms. Common usage can use the default global
8
+ # registry, via Net::SASL#authenticator.
9
+ class Registry
10
+
11
+ # Creates a new registry, which matches enabled SASL mechanisms with their
12
+ # implementations.
13
+ def initialize
14
+ @authenticators = {}
15
+ end
16
+
17
+ # Adds an authenticator class for use with #authenticator. +mechanism+ is
18
+ # the {SASL mechanism name}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
19
+ # supported by +authenticator+ (for instance, "+PLAIN+"). The
20
+ # +authenticator+ is an class which defines a +#process+ method to handle
21
+ # authentication with the server. See e.g. Net::SASL::PlainAuthenticator.
22
+ #
23
+ # If +mechanism+ refers to an existing authenticator, it will be replaced
24
+ # by the new one.
25
+ def add_authenticator(mechanism, authenticator)
26
+ @authenticators[mechanism.upcase] = authenticator
27
+ end
28
+
29
+ # Deletes an authenticator from the registry. This can be useful to
30
+ # implement a policy that prohibits the use of default mechanisms.
31
+ def remove_authenticator(mechanism)
32
+ @authenticators.delete(mechanism.upcase)
33
+ end
34
+
35
+ # Builds an authenticator in its initial state. +mechanism+ is the SASL
36
+ # mechanism name. All other arguments represent the credentials and other
37
+ # parameters or configuration, which will be passed directly to the chosen
38
+ # authenticator's +#new+ method. See Authenticator.new.
39
+ def authenticator(mechanism, authcid=nil, credentials=nil, authzid=nil, **kwargs)
40
+ mechanism = mechanism.upcase
41
+ unless @authenticators.key?(mechanism)
42
+ raise ArgumentError, 'unknown SASL mechanism - "%s"' % mechanism
43
+ end
44
+ @authenticators.fetch(mechanism)
45
+ .new(authcid, credentials, authzid, **kwargs)
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ module SASL
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/net-sasl.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/net/sasl/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "net-sasl"
7
+ spec.version = Net::SASL::VERSION
8
+ spec.authors = ["nicholas a. evans", "Shugo Maeda"]
9
+ spec.email = ["nicholas.evans@gmail.com", "shugo@ruby-lang.org"]
10
+
11
+ spec.summary = "Pluggable SASL mechanisms"
12
+ spec.description = "Pluggable mechanisms to support protocols which use SASL"
13
+ spec.homepage = "https://github.com/nevans/net-sasl"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) {
24
+ `git ls-files -z`.split("\x0").reject {|f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) {|f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "digest"
31
+ spec.add_dependency "strscan"
32
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-sasl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - nicholas a. evans
8
+ - Shugo Maeda
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2021-05-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: digest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: strscan
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: Pluggable mechanisms to support protocols which use SASL
43
+ email:
44
+ - nicholas.evans@gmail.com
45
+ - shugo@ruby-lang.org
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".github/workflows/main.yml"
51
+ - ".gitignore"
52
+ - ".rubocop.yml"
53
+ - CHANGELOG.md
54
+ - CODE_OF_CONDUCT.md
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - bin/console
60
+ - bin/setup
61
+ - lib/net/sasl.rb
62
+ - lib/net/sasl/authenticator.rb
63
+ - lib/net/sasl/cram_md5_authenticator.rb
64
+ - lib/net/sasl/digest_md5_authenticator.rb
65
+ - lib/net/sasl/login_authenticator.rb
66
+ - lib/net/sasl/plain_authenticator.rb
67
+ - lib/net/sasl/registry.rb
68
+ - lib/net/sasl/version.rb
69
+ - net-sasl.gemspec
70
+ homepage: https://github.com/nevans/net-sasl
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ homepage_uri: https://github.com/nevans/net-sasl
75
+ source_code_uri: https://github.com/nevans/net-sasl
76
+ changelog_uri: https://github.com/nevans/net-sasl/blob/main/CHANGELOG.md
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 2.5.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.1.6
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Pluggable SASL mechanisms
96
+ test_files: []