em-simple_telnet_server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/LICENSE +5 -0
- data/Makefile +2 -0
- data/README.gsl +58 -0
- data/README.md +141 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/em-simple_telnet_server.gemspec +26 -0
- data/lib/em-simple_telnet_server.rb +274 -0
- data/lib/em-simple_telnet_server/has_login.rb +140 -0
- data/lib/em-simple_telnet_server/version.rb +3 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5404a8e42d38865f170a211f1abbce3ea34507a4
|
4
|
+
data.tar.gz: 70e576cbe3a4ea0cba867dd0a9f9d7563b5e3dd7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8dd5559a1bf310e77da155f67d0b83b09153f6d4ed4f7d7ed93cf7596c505d46c6d38262c077ab76cc4e67b3155f271bd0332b85c5741bc23829bbd628047fb1
|
7
|
+
data.tar.gz: 425b2379c7be3923c0d98d519618069abfe3954f15b269802a641d5291f1487d65b585a15e2cf0b53f2ff331a1105fdc8a582fc79a4f608f8a91a5aca43205e0
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
Copyright (c) 2016, Patrik Wenger
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
4
|
+
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/Makefile
ADDED
data/README.gsl
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
.output "README.md"
|
2
|
+
.template 1
|
3
|
+
[![Build Status on Travis CI](https://travis-ci.org/paddor/em-simple_telnet_server.svg?branch=master)](https://travis-ci.org/paddor/em-simple_telnet_server?branch=master)
|
4
|
+
|
5
|
+
# SimpleTelnetServer
|
6
|
+
|
7
|
+
This gem provides a simple way to implement your own telnet server. It's useful
|
8
|
+
for example if you want to mock a telnet server in your telnet-related
|
9
|
+
integration tests.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'em-simple_telnet_server'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install em-simple_telnet_server
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Here's an example, right from [test/fake_machine.rb](https://github.com/paddor/em-simple_telnet_server/blob/master/test/fake_machine.rb).
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
.include "test/fake_machine.rb"
|
33
|
+
```
|
34
|
+
|
35
|
+
You can run it as follows. By default, it'll start listening on "localhost" and port 10023.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
EventMachine.run do
|
39
|
+
FakeMachine.start_server
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Development
|
44
|
+
|
45
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
46
|
+
|
47
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
48
|
+
|
49
|
+
## Contributing
|
50
|
+
|
51
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/paddor/em-simple_telnet_server.
|
52
|
+
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
The gem is available as open source under the terms of the [ISC License](http://opensource.org/licenses/ISC).
|
57
|
+
See the [LICENSE](https://github.com/paddor/em-simple_telnet_server/blob/master/LICENSE) file.
|
58
|
+
.endtemplate
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
[![Build Status on Travis CI](https://travis-ci.org/paddor/em-simple_telnet_server.svg?branch=master)](https://travis-ci.org/paddor/em-simple_telnet_server?branch=master)
|
2
|
+
|
3
|
+
# SimpleTelnetServer
|
4
|
+
|
5
|
+
This gem provides a simple way to implement your own telnet server. It's useful
|
6
|
+
for example if you want to mock a telnet server in your telnet-related
|
7
|
+
integration tests.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'em-simple_telnet_server'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install em-simple_telnet_server
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Here's an example, right from [test/fake_machine.rb](https://github.com/paddor/em-simple_telnet_server/blob/master/test/fake_machine.rb).
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'em-simple_telnet_server'
|
31
|
+
|
32
|
+
##
|
33
|
+
# This should just demonstrate what you can do with SimpleTelnetServer.
|
34
|
+
#
|
35
|
+
class FakeMachine < SimpleTelnetServer::Connection
|
36
|
+
# adds simple authentication and authorization
|
37
|
+
include SimpleTelnetServer::HasLogin
|
38
|
+
|
39
|
+
has_option :command_prompt, "fake$ "
|
40
|
+
has_option :login_prompt, "fakelogin: "
|
41
|
+
has_option :password_prompt, "fakepassword: "
|
42
|
+
|
43
|
+
has_login "fakeuser", "fakepass" # default is ":user" role
|
44
|
+
has_login "fakeroot", "fakerootpass", role: :admin
|
45
|
+
|
46
|
+
# Echo command.
|
47
|
+
has_command(/^s*echo (.*)/) do |what|
|
48
|
+
send_output(what) # this also sends a prompt back
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send command prompt on return.
|
52
|
+
has_command(/^s*$/, :send_command_prompt)
|
53
|
+
|
54
|
+
# This is the callback that is called right after authorization. You could
|
55
|
+
# initialize your code here.
|
56
|
+
def on_authorization
|
57
|
+
send_output "Hello #{entered_username}! You're authorized now."
|
58
|
+
end
|
59
|
+
|
60
|
+
# Just to demonstrate the ability to use methods as command actions instead
|
61
|
+
# of blocks. This method will be called directly if the command "count up"
|
62
|
+
# is called. It also supports commands in the form of "count up 5".
|
63
|
+
#
|
64
|
+
# See the call to {.has_command} below.
|
65
|
+
def count_up(step)
|
66
|
+
step ||= 1
|
67
|
+
@count_up_number ||= 0
|
68
|
+
@count_up_number += step
|
69
|
+
send_output "The new number is #@count_up_number."
|
70
|
+
end
|
71
|
+
has_command("count up(?:s+(d+))", :count_up)
|
72
|
+
|
73
|
+
# Simulates a slow command which could be used to test timeouts.
|
74
|
+
# This can be invoked using "slow command".
|
75
|
+
def slow_command
|
76
|
+
EventMachine::Timer.new(3) { send_output "This is the output." }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Recognizes commands like "sleep 3" and "sleep 1.5" and actually performs
|
80
|
+
# the sleep.
|
81
|
+
has_command(/^sleeps+(d+(?:.d+)?)s*$/) do |seconds|
|
82
|
+
EventMachine::Timer.new(seconds.to_f) do
|
83
|
+
send_output "This is the output."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Simulates a command that ends in a very weird prompt.
|
88
|
+
#
|
89
|
+
# This is just to demonstrate the use of {#send_data}. It won't send the
|
90
|
+
# default command prompt like {#send_output} would.
|
91
|
+
has_command(/^weirds*$/) do
|
92
|
+
send_data("some
|
93
|
+
output
|
94
|
+
weird-prompt| ")
|
95
|
+
end
|
96
|
+
|
97
|
+
# logout
|
98
|
+
has_command(/^byes*$/, :close_connection)
|
99
|
+
|
100
|
+
# tanslate every command to a method call, if the method exists
|
101
|
+
# @note This is probably dangerous. But whatever, it's telnet.
|
102
|
+
has_command(/^([w ]+)$/) do |command|
|
103
|
+
method = command.gsub(/ /, '_')
|
104
|
+
if self.respond_to? method
|
105
|
+
self.send method
|
106
|
+
else
|
107
|
+
raise UnknownCommand, command
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Simplest test command ever. This will only be invokable because of the
|
112
|
+
# catch-all-and-translate-to-method-calls block above.
|
113
|
+
def foo
|
114
|
+
send_output("bar")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
You can run it as follows. By default, it'll start listening on "localhost" and port 10023.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
EventMachine.run do
|
123
|
+
FakeMachine.start_server
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
## Development
|
128
|
+
|
129
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
130
|
+
|
131
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
132
|
+
|
133
|
+
## Contributing
|
134
|
+
|
135
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/paddor/em-simple_telnet_server.
|
136
|
+
|
137
|
+
|
138
|
+
## License
|
139
|
+
|
140
|
+
The gem is available as open source under the terms of the [ISC License](http://opensource.org/licenses/ISC).
|
141
|
+
See the [LICENSE](https://github.com/paddor/em-simple_telnet_server/blob/master/LICENSE) file.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "em-simple_telnet_server"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'em-simple_telnet_server/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "em-simple_telnet_server"
|
8
|
+
spec.version = SimpleTelnetServer::VERSION
|
9
|
+
spec.authors = ["Patrik Wenger"]
|
10
|
+
spec.email = ["paddor@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Simple telnet server on EventMachine"
|
13
|
+
spec.description = "A simple way to implement your own telnet server on" +
|
14
|
+
" EventMachine"
|
15
|
+
spec.homepage = "http://github.com/paddor/em-simple_telnet_server"
|
16
|
+
spec.license = "ISC"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
spec.add_development_dependency 'minitest-reporters'
|
25
|
+
spec.add_dependency('eventmachine', '>= 1.0.0')
|
26
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require_relative "em-simple_telnet_server/version"
|
2
|
+
require_relative "em-simple_telnet_server/has_login"
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
# A basic Telnet server implemented with EventMachine.
|
6
|
+
class SimpleTelnetServer::Connection < EventMachine::Connection
|
7
|
+
# @return [Hash<Symbol, Object>] default values for (telnet) options
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
port: 10023,
|
10
|
+
command_prompt: "$ ",
|
11
|
+
login_prompt: "login: ",
|
12
|
+
password_prompt: "password: ",
|
13
|
+
}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Starts the server on address _addr_ and port _port_.
|
18
|
+
def start_server(addr = 'localhost', port = options[:port])
|
19
|
+
EventMachine.start_server(addr, port, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the option _opt_ to value.
|
23
|
+
# @param opt [Symbol] option key
|
24
|
+
# @param value [Object] anything
|
25
|
+
def has_option(opt, value)
|
26
|
+
options[opt] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the (telnet) options for this class. If they are not initialized
|
30
|
+
# yet, the ones from superclass (or SimpleTelnetServer) are duplicated.
|
31
|
+
def options
|
32
|
+
@options ||= if top_class?
|
33
|
+
DEFAULT_OPTIONS
|
34
|
+
else
|
35
|
+
superclass.options
|
36
|
+
end.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
# Registers an action for the command _cmd_ (which can be a Regexp, #===
|
40
|
+
# will be used). _action_ can be a Symbol referring to an instance method
|
41
|
+
# or an instance of Proc. If _action_ is not specified, the given block is
|
42
|
+
# used.
|
43
|
+
#
|
44
|
+
# If _cmd_ is a Regexp, all captures (MatchData#captures) will be passed
|
45
|
+
# to the block/method call (see {#run_command}).
|
46
|
+
#
|
47
|
+
# @param cmd [String, Regexp] fixed command or regular expression that
|
48
|
+
# matches a command and its arguments in capture groups
|
49
|
+
# @param action [Symbol] the method to call. if given, the block passed is
|
50
|
+
# ignored
|
51
|
+
def has_command(cmd, action = nil, &blk)
|
52
|
+
commands[cmd] = action || blk
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the Hash of registered commands. If they are not initialized
|
56
|
+
# yet (@commands), the one from superclass is used. If we're TelnetServer
|
57
|
+
# itself, an empty Hash is used.
|
58
|
+
#
|
59
|
+
# @return [Hash{String, Regexp => Symbol, Proc}]
|
60
|
+
def commands
|
61
|
+
@commands ||= if top_class?
|
62
|
+
{}
|
63
|
+
else
|
64
|
+
superclass.commands.dup # copy from superclass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean] whether we've reached the top of the relevant
|
69
|
+
# hieararchy
|
70
|
+
def top_class?
|
71
|
+
self == SimpleTelnetServer::Connection
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# def send_data(data)
|
76
|
+
# warn "Server: >>> #{data.inspect}"
|
77
|
+
# super
|
78
|
+
# end
|
79
|
+
|
80
|
+
|
81
|
+
# custom handler of buffer content
|
82
|
+
# @return [Proc, #call] will be passed the content of the buffer
|
83
|
+
attr_accessor :custom_handler
|
84
|
+
|
85
|
+
# Called by EventMachine when a new connection attempt is made to this
|
86
|
+
# server (immediately after calling {#initialize}).
|
87
|
+
#
|
88
|
+
# Checks if {#needs_authentication?}, which returns +false+ if not
|
89
|
+
# overridden. If it authentication is needed, it'll initiate the login
|
90
|
+
# procedure (send login prompt, get username, get password, ...).
|
91
|
+
#
|
92
|
+
# Otherwise, any peer is authorized right away.
|
93
|
+
def post_init
|
94
|
+
@buffer = ""
|
95
|
+
if needs_authentication?
|
96
|
+
initiate_authentication
|
97
|
+
else
|
98
|
+
authorize # login anybody
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @abstract
|
103
|
+
# If authentication is not required, there won't be a login procedure and
|
104
|
+
# any peer is automatically logged in after connecting.
|
105
|
+
# @return [Boolean] whether this telnet server requires authentication
|
106
|
+
def needs_authentication?
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
# Called by EventMachine when new data is received. Appends _data_ to the
|
111
|
+
# buffer (@buffer). Calls {#process_buffer} if @buffer content ends with
|
112
|
+
# newline.
|
113
|
+
def receive_data data
|
114
|
+
# warn "Server: <<< #{data.inspect}"
|
115
|
+
@buffer << data
|
116
|
+
|
117
|
+
# work only with complete commands (ending with newline)
|
118
|
+
process_buffer if @buffer.end_with? "\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [Boolean] whether the user is logged in
|
122
|
+
def authorized?
|
123
|
+
@connection_state == :authorized
|
124
|
+
end
|
125
|
+
|
126
|
+
# @abstract
|
127
|
+
# Called by EventMachine after the connection has been closed.
|
128
|
+
def unbind
|
129
|
+
end
|
130
|
+
|
131
|
+
# @abstract
|
132
|
+
# Called automatically when a received command is not known (no matching
|
133
|
+
# entry in @commands) and sends back an error message.
|
134
|
+
#
|
135
|
+
# @param command [String] the command that is not known
|
136
|
+
def command_not_known(command)
|
137
|
+
send_output "Command #{command.inspect} is not known."
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the telnet options for this telnet server.
|
141
|
+
def options
|
142
|
+
self.class.options
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the recognized commands for this telnet server.
|
146
|
+
def commands
|
147
|
+
self.class.commands
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Sends the command prompt.
|
153
|
+
def send_command_prompt
|
154
|
+
send_data options[:command_prompt]
|
155
|
+
end
|
156
|
+
|
157
|
+
# Sends _output_ and then the command prompt.
|
158
|
+
# Appens new line to output first, if it doesn't have one yet, unless it's
|
159
|
+
# the empty string.
|
160
|
+
# @param output [String] output to send before command prompt
|
161
|
+
def send_output(output)
|
162
|
+
output += "\n" unless output.end_with? "\n" or output.empty?
|
163
|
+
send_data output
|
164
|
+
send_command_prompt
|
165
|
+
end
|
166
|
+
|
167
|
+
# Processes the content of @buffer.
|
168
|
+
#
|
169
|
+
# If a {#custom_handler} is defined, it's called with the current buffer
|
170
|
+
# contents. The handler will be removed, so if it has to stay, it has to
|
171
|
+
# re-add itself.
|
172
|
+
#
|
173
|
+
# If the user is authorized, the commands in the buffer are executed.
|
174
|
+
#
|
175
|
+
# If the user isn't authorized, {#process_spam} is called, which does
|
176
|
+
# nothing by default.
|
177
|
+
#
|
178
|
+
# Ensures that the buffer is cleared.
|
179
|
+
def process_buffer
|
180
|
+
if handler = custom_handler
|
181
|
+
self.custom_handler = nil
|
182
|
+
handler.(@buffer)
|
183
|
+
|
184
|
+
elsif authorized?
|
185
|
+
run_commands
|
186
|
+
else
|
187
|
+
process_spam
|
188
|
+
end
|
189
|
+
ensure
|
190
|
+
@buffer.clear
|
191
|
+
end
|
192
|
+
|
193
|
+
# Authorizes the user. This is done by setting @connection_state to
|
194
|
+
# +:authorized+, calling the {#on_authorization} hook method, and sendng him
|
195
|
+
# the command prompt.
|
196
|
+
def authorize
|
197
|
+
@connection_state = :authorized
|
198
|
+
on_authorization
|
199
|
+
send_command_prompt
|
200
|
+
end
|
201
|
+
|
202
|
+
# @abstract
|
203
|
+
# Called right after authorization. You can override this method to
|
204
|
+
# initialize your code.
|
205
|
+
#
|
206
|
+
# Using {#initialize} instead is a bit clunky because it's expected to take
|
207
|
+
# EventMachine-specific arguments and happens before the user is authorized.
|
208
|
+
# Same goes for {#post_init}. For both you'd have to remember to call
|
209
|
+
# +super+.
|
210
|
+
def on_authorization
|
211
|
+
end
|
212
|
+
|
213
|
+
# Raised when a command has been received that doesn't match any registered
|
214
|
+
# command.
|
215
|
+
class UnknownCommand < RuntimeError
|
216
|
+
def initialize(command) @command = command end
|
217
|
+
attr_reader :command
|
218
|
+
end
|
219
|
+
|
220
|
+
# Runs the commands in the buffer. Will call {#run_command} for each command
|
221
|
+
# (line), no matter if it is recognized or not. If {UnknownCommand} is
|
222
|
+
# raised, it's handled using {#command_not_known}.
|
223
|
+
def run_commands
|
224
|
+
@buffer.lines.each do |command|
|
225
|
+
run_command(command.chomp)
|
226
|
+
end
|
227
|
+
rescue UnknownCommand
|
228
|
+
command_not_known $!.command
|
229
|
+
end
|
230
|
+
|
231
|
+
# Runs a command.
|
232
|
+
#
|
233
|
+
# Stores _command_ into @current_command for later use and looks it up. If
|
234
|
+
# it finds an action for it, executes the action.
|
235
|
+
#
|
236
|
+
# If the matching command pattern is a Regexp, the captures are passed to
|
237
|
+
# the action.
|
238
|
+
#
|
239
|
+
# @param command [String] command to run
|
240
|
+
# @raise [UnknownCommand] if the command is unknown
|
241
|
+
def run_command(command)
|
242
|
+
@current_command = command
|
243
|
+
if pair = commands.find { |pattern,| pattern === command }
|
244
|
+
pattern, action = pair
|
245
|
+
args = $~.captures if pattern.is_a? Regexp
|
246
|
+
execute_action(action, args) if action
|
247
|
+
else
|
248
|
+
raise UnknownCommand, command
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Invokes _action_ along with the given arguments.
|
253
|
+
#
|
254
|
+
# @param action [Proc, Symbol] code or a method on this server to call
|
255
|
+
# @param params [Array<Object>, nil] arguments for action (passed with splat
|
256
|
+
# operator)
|
257
|
+
# @raise [ArgumentError] if action is invalid
|
258
|
+
def execute_action(action, args = nil)
|
259
|
+
case action
|
260
|
+
when Proc
|
261
|
+
self.instance_exec(*args, &action)
|
262
|
+
when Symbol
|
263
|
+
self.send(action, *args)
|
264
|
+
else
|
265
|
+
raise ArgumentError, "invalid action #{action.inspect}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# @abstract
|
270
|
+
# Called when data has been received while user isn't authorized. This could
|
271
|
+
# be used to {#close_connection}.
|
272
|
+
def process_spam
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Adds functionality for simple authentication and authorization.
|
2
|
+
#
|
3
|
+
# By default, login credentials are defined right in the class definition
|
4
|
+
# using {ClassMethods::has_login}. If something more dynamic is needed, just
|
5
|
+
# override {#authenticate}.
|
6
|
+
#
|
7
|
+
# After authentication, {#entered_username}, {#entered_password}, and
|
8
|
+
# {#authorized_role} are set.
|
9
|
+
module SimpleTelnetServer::HasLogin
|
10
|
+
# Extends klass with {ClassMethods}.
|
11
|
+
def self.included(klass)
|
12
|
+
klass.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# @return [Hash{login_type Symbol => Array<(username String, password
|
18
|
+
# String)>}] registered (read/write) login credentials
|
19
|
+
def login_credentials
|
20
|
+
options[:login_credentials] ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds a pair of login credentials.
|
24
|
+
# @param username [String] username
|
25
|
+
# @param password [String] password
|
26
|
+
# @param role [Symbol] (:user) the associated role name
|
27
|
+
def has_login(username, password, role: :user)
|
28
|
+
login_credentials[role] = [ username, password ]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] the entered username
|
33
|
+
# @note This doesn't necessarily mean the user is logged in. Use
|
34
|
+
# {#authorized?} to check for that.
|
35
|
+
attr_reader :entered_username
|
36
|
+
|
37
|
+
# @return [String] the password username
|
38
|
+
attr_reader :entered_password
|
39
|
+
|
40
|
+
# @return [Symbol] the associated role of the valid login credentials
|
41
|
+
attr_reader :authorized_role
|
42
|
+
|
43
|
+
# @return [true] true, as this module is about authentication
|
44
|
+
def needs_authentication?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initiates authentication. This means setting the connection state to
|
49
|
+
# +waiting_for_username+ and sending the login prompt.
|
50
|
+
def initiate_authentication
|
51
|
+
@connection_state = :waiting_for_username
|
52
|
+
send_login_prompt
|
53
|
+
end
|
54
|
+
|
55
|
+
# If the user was requested to enter his username, the username is read.
|
56
|
+
# If the user was requested to enter his password, the password is read.
|
57
|
+
#
|
58
|
+
# Otherwise, the normal (+super+) behavior proceeds.
|
59
|
+
#
|
60
|
+
# In all cases, it ensures that the buffer is cleared at the end.
|
61
|
+
#
|
62
|
+
def process_buffer
|
63
|
+
if waiting_for_username?
|
64
|
+
read_username_from_buffer
|
65
|
+
|
66
|
+
elsif waiting_for_password?
|
67
|
+
read_password_from_buffer
|
68
|
+
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
ensure
|
73
|
+
@buffer.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sends the login prompt.
|
77
|
+
def send_login_prompt
|
78
|
+
send_data options[:login_prompt]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sends the password prompt.
|
82
|
+
def send_password_prompt
|
83
|
+
send_data options[:password_prompt]
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [Boolean] whether we are waiting for the user to enter the username
|
87
|
+
def waiting_for_username?
|
88
|
+
@connection_state == :waiting_for_username
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Boolean] whether we are waiting for the user to enter the password
|
92
|
+
def waiting_for_password?
|
93
|
+
@connection_state == :waiting_for_password
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reads the password from buffer and authorizes the user
|
97
|
+
# if he can be authenticated. Otherwise the
|
98
|
+
# connection state is set back to +:waiting_for_username+ and the login
|
99
|
+
# prompt is sent.
|
100
|
+
def read_password_from_buffer
|
101
|
+
@entered_password = @buffer.chomp
|
102
|
+
if role = authenticate(@entered_username, @entered_password)
|
103
|
+
@authorized_role = role
|
104
|
+
authorize
|
105
|
+
else
|
106
|
+
@connection_state = :waiting_for_username
|
107
|
+
@entered_username = @entered_password = nil
|
108
|
+
send_login_failed
|
109
|
+
send_login_prompt
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Reads the username from buffer. Sends the password prompt
|
114
|
+
# afterwards ({#send_password_prompt}) and sets the connection state to
|
115
|
+
# +:waiting_for_password+.
|
116
|
+
def read_username_from_buffer
|
117
|
+
@entered_username = @buffer.strip
|
118
|
+
send_password_prompt
|
119
|
+
@connection_state = :waiting_for_password
|
120
|
+
end
|
121
|
+
|
122
|
+
# Sends the message "Sorry, please try again." and the login prompt.
|
123
|
+
def send_login_failed
|
124
|
+
send_data "Sorry, please try again.\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Checks _user_ and _pass_ against all known login credentials.
|
129
|
+
#
|
130
|
+
# @param user [String] entered username
|
131
|
+
# @param pass [String] entered password
|
132
|
+
# @return [Symbol] associated role, if credentials are known
|
133
|
+
# @return [nil] if credentials are not known
|
134
|
+
def authenticate(user, pass)
|
135
|
+
self.class.login_credentials.each do |role, credentials|
|
136
|
+
return role if credentials == [ user, pass ]
|
137
|
+
end
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-simple_telnet_server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrik Wenger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: eventmachine
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.0.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.0.0
|
83
|
+
description: A simple way to implement your own telnet server on EventMachine
|
84
|
+
email:
|
85
|
+
- paddor@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE
|
94
|
+
- Makefile
|
95
|
+
- README.gsl
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/setup
|
100
|
+
- em-simple_telnet_server.gemspec
|
101
|
+
- lib/em-simple_telnet_server.rb
|
102
|
+
- lib/em-simple_telnet_server/has_login.rb
|
103
|
+
- lib/em-simple_telnet_server/version.rb
|
104
|
+
homepage: http://github.com/paddor/em-simple_telnet_server
|
105
|
+
licenses:
|
106
|
+
- ISC
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.5.1
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Simple telnet server on EventMachine
|
128
|
+
test_files: []
|
129
|
+
has_rdoc:
|