telnet-server 1.0.2-java

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.
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ # The Telnet module
17
+ module Telnet
18
+ java_import Java::io.netty.channel.ChannelFutureListener
19
+ java_import Java::io.netty.channel.ChannelOption
20
+ java_import Java::io.netty.channel.SimpleChannelInboundHandler
21
+ java_import java.net.InetAddress
22
+
23
+ # The Handler class implements the barest minimum
24
+ # handler methods to support a telnet server.
25
+ class Handler < SimpleChannelInboundHandler
26
+ def initialize(options = {}, &handler)
27
+ super()
28
+ @options = options
29
+ @handler = handler
30
+ end
31
+
32
+ def isSharable
33
+ true
34
+ end
35
+
36
+ def channelActive(ctx)
37
+ host_name = InetAddress.local_host.host_name
38
+ # Send greeting for a new connection.
39
+ ctx.write "Welcome to #{host_name}!\r\n"
40
+ ctx.write "The time is #{Time.now}.\r\n"
41
+ ctx.write @options[:prompt]
42
+ ctx.flush
43
+ end
44
+
45
+ # Please keep in mind that this method will be renamed to
46
+ # messageReceived(ChannelHandlerContext, I) in 5.0.
47
+ #
48
+ # java_signature 'protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception'
49
+ def channelRead0(ctx, msg)
50
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
51
+ messageReceived(ctx, msg)
52
+ end
53
+
54
+ def messageReceived(ctx, msg)
55
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
56
+ msg&.chomp!
57
+ log.info "Received message: #{msg}"
58
+ return super(ctx, msg) unless respond_to?(:handle_message)
59
+ handle_message(ctx, msg)
60
+ end
61
+
62
+ # Generate and write a response.
63
+ # Writing to a ChannelBuffer is not needed here.
64
+ # The encoder will do the conversion.
65
+ def handle_message(ctx, message)
66
+ request = (message&.to_s || '').strip
67
+ return handle_empty_request(ctx) if request.empty?
68
+ return close(ctx) if quit_command?(request)
69
+ ctx.write "Server echo: #{request}\r\n"
70
+ ctx.write @options[:prompt]
71
+ end
72
+
73
+ def handle_empty_request(ctx)
74
+ ctx.write "Please type something.\r\n"
75
+ ctx.write @options[:prompt]
76
+ end
77
+
78
+ def quit_command?(request)
79
+ @options[:quit_commands].include?(request.to_s.downcase.to_sym)
80
+ end
81
+
82
+ def close(ctx)
83
+ future = ctx.write "Bye!\r\n"
84
+ # Close the connection
85
+ future.addListener(ChannelFutureListener::CLOSE)
86
+ end
87
+
88
+ def channelReadComplete(ctx)
89
+ log.trace "##{__method__} channel: #{ctx.channel}"
90
+ ctx.flush
91
+ end
92
+
93
+ def exceptionCaught(ctx, cause)
94
+ cause.printStackTrace()
95
+ ctx.close
96
+ end
97
+ end
98
+ end
99
+ # module Telnet
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+ require 'tcp-server'
16
+
17
+ require_relative 'handler'
18
+
19
+ # The Telnet module
20
+ module Telnet
21
+ # The Server class sets up the netty server
22
+ class Server < Server::Server
23
+ def initialize(params = {}, &block)
24
+ super
25
+ channel_initializer << ::Telnet::Handler.new(@options)
26
+ end
27
+
28
+ def bootstrap
29
+ super
30
+ @bootstrap.childOption(Java::io.netty.channel.ChannelOption::SO_KEEPALIVE, true)
31
+ end
32
+ end
33
+ end
34
+ # module Telnet
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ # The Telnet module
14
+ module Telnet
15
+ VERSION = '1.0.2'.freeze
16
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require_relative 'telnet_server'
@@ -0,0 +1,518 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ # encoding: utf-8
4
+ # frozen_string_literal: false
5
+
6
+ # -*- mode: ruby -*-
7
+ # vi: set ft=ruby :
8
+
9
+ # =begin
10
+ #
11
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
12
+ #
13
+ # =end
14
+
15
+ require 'logger'
16
+ require 'optparse'
17
+
18
+ require 'java'
19
+ require 'netty'
20
+
21
+ require_relative 'log'
22
+ require_relative 'telnet/version'
23
+
24
+ # Simple telnet client
25
+
26
+ # The Telnet module
27
+ module Telnet
28
+ def client_config
29
+ @client_config ||= {
30
+ host: '0.0.0.0',
31
+ port: 21,
32
+ ssl: false,
33
+ log_level: Logger::INFO,
34
+ quit_commands: %i[bye cease desist exit leave quit stop terminate],
35
+ max_frame_length: 8192,
36
+ delimiter: Java::io.netty.handler.codec.Delimiters.nulDelimiter
37
+ }
38
+ end
39
+ module_function :client_config
40
+ end
41
+ # module Telnet
42
+
43
+ # The Telnet module
44
+ module Telnet
45
+ CHANNEL_TYPE = Java::io.netty.channel.socket.nio.NioSocketChannel.java_class
46
+ java_import Java::io.netty.bootstrap.Bootstrap
47
+ java_import Java::io.netty.channel.nio.NioEventLoopGroup
48
+ java_import Java::io.netty.handler.ssl.SslContext
49
+ java_import Java::io.netty.handler.ssl.SslContextBuilder
50
+ java_import Java::io.netty.handler.ssl.util.InsecureTrustManagerFactory
51
+
52
+ # The ClientInitializationMethods module
53
+ module ClientInitializationMethods
54
+ def init(options)
55
+ @options = options
56
+ @host = options[:host]
57
+ @port = options[:port]
58
+ @queue = java.util.concurrent.LinkedBlockingQueue.new
59
+ end
60
+
61
+ def bootstrap
62
+ @bootstrap = Bootstrap.new
63
+ @bootstrap.group(client_group)
64
+ @bootstrap.channel(::Telnet::CHANNEL_TYPE)
65
+ @bootstrap.handler(logging_handler) if @options[:log_requests]
66
+ @bootstrap.handler(channel_initializer)
67
+ end
68
+
69
+ def client_group
70
+ @client_group ||= NioEventLoopGroup.new
71
+ end
72
+
73
+ def logging_handler
74
+ @logging_handler ||= LoggingHandler.new(LogLevel::INFO)
75
+ end
76
+
77
+ def channel_initializer
78
+ @channel_initializer ||= ::Telnet::ClientChannelInitializer.new(@host, @port, @options)
79
+ end
80
+
81
+ def configure_handlers(*handlers, &block)
82
+ channel_initializer.default_handler.add_listener(self)
83
+ channel_initializer.default_handler.listeners.addAll(handlers)
84
+ @user_app = block
85
+ @application_handler = lambda do |ctx, msg|
86
+ if @user_app.nil? || @user_app.arity == 1
87
+ @queue.add(msg)
88
+ else
89
+ @user_app.call(ctx, msg)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ # module ClientInitializationMethods
95
+ end
96
+ # module Telnet
97
+
98
+ # The Telnet module
99
+ module Telnet
100
+ java_import Java::io.netty.buffer.Unpooled
101
+ java_import Java::io.netty.channel.AbstractChannel
102
+
103
+ # The ClientInstanceMethods module
104
+ module ClientInstanceMethods
105
+ def puts(msg)
106
+ wait_until_channel_is_active
107
+ msg.chomp!
108
+ log.trace "#puts msg: #{msg.inspect}"
109
+ return if msg.nil? || msg.empty?
110
+ @last_write_future = @channel.writeAndFlush("#{msg}\r\n")
111
+ end
112
+
113
+ def gets(timeout = nil)
114
+ log.debug 'Waiting for response from server'
115
+ timeout.nil? ? @queue.take : @queue.take(timeout)
116
+ rescue StandardError => e
117
+ warn "Unexpected error waiting for message: #{e.message}"
118
+ nil
119
+ end
120
+
121
+ def wait_until_channel_is_active(timeout = 5, give_up = Time.now + timeout)
122
+ sleep 0.1 until @channel.active? || Time.now > give_up
123
+ end
124
+
125
+ def connect
126
+ @channel = bootstrap.connect(@host, @port).sync().channel()
127
+ @last_write_future&.sync()
128
+ rescue AbstractChannel::AnnotatedConnectException => e
129
+ raise e.message
130
+ rescue StandardError => e
131
+ raise "Connection failure: #{e.message}"
132
+ end
133
+
134
+ def close(channel = @channel)
135
+ log.debug 'Closing primary channel'
136
+ channel.closeFuture().sync()
137
+ ensure
138
+ shutdown
139
+ end
140
+
141
+ def shutdown
142
+ log.debug 'Shutting down gracefully'
143
+ @client_group&.shutdownGracefully()
144
+ ensure
145
+ client_has_shut_down
146
+ end
147
+
148
+ def session
149
+ when_client_has_shut_down(@client_group) do |group|
150
+ log.debug "Channel group has shut down: #{group.inspect}"
151
+ end
152
+ @user_app.nil? ? read_user_commands : invoke_user_app
153
+ end
154
+
155
+ def invoke_user_app
156
+ @user_app&.call(self)
157
+ ensure
158
+ close
159
+ end
160
+
161
+ def shut_down_callbacks
162
+ @shut_down_callbacks ||= []
163
+ end
164
+
165
+ def when_client_has_shut_down(*args, &block)
166
+ shut_down_callbacks << {
167
+ block: block,
168
+ args: args
169
+ }
170
+ self
171
+ end
172
+
173
+ def client_has_shut_down
174
+ shut_down_callbacks.take_while do |callback|
175
+ callback[:block]&.call(*callback.fetch(:args, []))
176
+ end
177
+ rescue StandardError => e
178
+ log.error e.message
179
+ end
180
+
181
+ def channel_unregistered(ctx)
182
+ log.trace "##{__method__} channel: #{ctx.channel}"
183
+ shutdown
184
+ end
185
+
186
+ def channel_active(ctx)
187
+ log.info "Connected to #{ctx.channel().remoteAddress0()}"
188
+ end
189
+
190
+ def channel_inactive(ctx)
191
+ log.info "Disconnected from #{ctx.channel().remoteAddress0()}"
192
+ end
193
+
194
+ def message_received(ctx, message)
195
+ log.debug "##{__method__} message: #{message}"
196
+ notify :message_received, ctx, message
197
+ if @application_handler.nil?
198
+ $stdout.print message.chomp unless message.nil?
199
+ else
200
+ @application_handler.call(ctx, message)
201
+ end
202
+ end
203
+
204
+ def execute_command(str, client = self)
205
+ return if str.empty?
206
+ client.puts "#{str}\r\n"
207
+ close if @options[:quit_commands].include?(str.downcase.to_sym)
208
+ end
209
+
210
+ def read_user_commands
211
+ log.trace 'Reading user commands'
212
+ loop do
213
+ log.debug 'Waiting for user input...'
214
+ input = $stdin.gets&.chomp
215
+ raise 'Poll failure from stdin' if input.nil?
216
+ break unless @channel.active?
217
+ break if execute_command(input).is_a?(AbstractChannel::CloseFuture)
218
+ end
219
+ end
220
+ end
221
+ # module ClientInstanceMethods
222
+ end
223
+ # module Telnet
224
+
225
+ # The Telnet module
226
+ module Telnet
227
+ # The Listenable module
228
+ module Listenable
229
+ def listeners
230
+ @listeners ||= java.util.concurrent.CopyOnWriteArrayList.new
231
+ end
232
+
233
+ def add_listener(listener)
234
+ listeners << listener
235
+ end
236
+
237
+ def remove_listener(listener)
238
+ listeners.delete(listener)
239
+ end
240
+
241
+ def notify(message, *args)
242
+ return if listeners.empty?
243
+ log.trace "Notifying listeners (#{listeners}) of message: #{message}"
244
+ listeners.each do |listener|
245
+ listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ # The Telnet module
252
+ module Telnet
253
+ java_import Java::io.netty.channel.SimpleChannelInboundHandler
254
+
255
+ # The ModularHandler class
256
+ class ModularHandler < SimpleChannelInboundHandler
257
+ include ::Telnet::Listenable
258
+
259
+ def isSharable
260
+ true
261
+ end
262
+
263
+ def channelRegistered(ctx)
264
+ log.trace "##{__method__} channel: #{ctx.channel}"
265
+ notify :channel_registered, ctx
266
+ super(ctx)
267
+ end
268
+
269
+ def channelUnregistered(ctx)
270
+ log.trace "##{__method__} channel: #{ctx.channel}"
271
+ notify :channel_unregistered, ctx
272
+ super(ctx)
273
+ end
274
+
275
+ def channelActive(ctx)
276
+ ::Telnet.log.info "Channel active #{ctx.channel}"
277
+ notify :channel_active, ctx
278
+ super(ctx)
279
+ end
280
+
281
+ def channelInactive(ctx)
282
+ log.trace "##{__method__} channel: #{ctx.channel}"
283
+ notify :channel_inactive, ctx
284
+ super(ctx)
285
+ end
286
+
287
+ def messageReceived(ctx, msg)
288
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
289
+ notify :message_received, ctx, msg
290
+ end
291
+
292
+ def channelRead(ctx, msg)
293
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
294
+ notify :channel_read, ctx, msg
295
+ super(ctx, msg)
296
+ end
297
+
298
+ # Please keep in mind that this method will be renamed to
299
+ # messageReceived(ChannelHandlerContext, I) in 5.0.
300
+ #
301
+ # java_signature 'protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception'
302
+ def channelRead0(ctx, msg)
303
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
304
+ messageReceived(ctx, msg)
305
+ end
306
+
307
+ def channelReadComplete(ctx)
308
+ log.trace "##{__method__} channel: #{ctx.channel}"
309
+ notify :channel_read_complete, ctx
310
+ super(ctx)
311
+ end
312
+
313
+ def channelWritabilityChanged(ctx)
314
+ log.trace "##{__method__} channel: #{ctx.channel}"
315
+ notify :channel_writability_changed, ctx
316
+ super(ctx)
317
+ end
318
+
319
+ def userEventTriggered(ctx, evt)
320
+ log.trace "##{__method__} channel: #{ctx.channel}, event: #{evt}"
321
+ notify :user_event_triggered, ctx, evt
322
+ super(ctx, evt)
323
+ end
324
+
325
+ def exceptionCaught(ctx, cause)
326
+ ::Telnet.log.warn "##{__method__} channel: #{ctx.channel}, cause: #{cause.message}"
327
+ listeners = notify :exception_caught, ctx, cause
328
+ super(ctx, cause) if listeners.empty?
329
+ end
330
+
331
+ IdentiferTemplate = '#<%<class>s:0x%<id>s>'.freeze
332
+
333
+ def to_s
334
+ format(IdentiferTemplate, class: self.class.name, id: object_id.to_s(16))
335
+ end
336
+ alias inspect to_s
337
+ end
338
+ # class ModularHandler
339
+ end
340
+ # module Telnet
341
+
342
+ # The Telnet module
343
+ module Telnet
344
+ java_import Java::io.netty.handler.codec.DelimiterBasedFrameDecoder
345
+ java_import Java::io.netty.handler.codec.string.StringDecoder
346
+ java_import Java::io.netty.handler.codec.string.StringEncoder
347
+
348
+ # The ClientChannelInitializer class
349
+ class ClientChannelInitializer < Java::io.netty.channel.ChannelInitializer
350
+ attr_accessor :decoder, :encoder
351
+
352
+ def initialize(host, port, options = {})
353
+ super()
354
+ @host = host
355
+ @port = port
356
+ @options = options
357
+ @decoder = StringDecoder.new
358
+ @encoder = StringEncoder.new
359
+ end
360
+
361
+ def default_handler
362
+ @default_handler ||= ::Telnet::ModularHandler.new
363
+ end
364
+
365
+ def initChannel(channel)
366
+ pipeline = channel.pipeline()
367
+ pipeline.addLast(ssl_handler(channel)) if @options[:ssl]
368
+ # pipeline.addLast(frame_decoder)
369
+ pipeline.addLast(decoder)
370
+ pipeline.addLast(encoder)
371
+ pipeline.addLast(default_handler)
372
+ end
373
+
374
+ def frame_decoder
375
+ DelimiterBasedFrameDecoder.new(@options[:max_frame_length], @options[:delimiter])
376
+ end
377
+
378
+ def ssl_handler(channel)
379
+ ssl_context&.newHandler(channel.alloc(), @host, @port)
380
+ end
381
+
382
+ def ssl_context
383
+ nil # TODO: Implement
384
+ end
385
+ end
386
+ # class ClientChannelInitializer
387
+ end
388
+ # module Telnet
389
+
390
+ # The Telnet module
391
+ module Telnet
392
+ # The Telnet::Client class
393
+ class Client
394
+ include ::Telnet::ClientInitializationMethods
395
+ include ::Telnet::ClientInstanceMethods
396
+ include ::Telnet::Listenable
397
+
398
+ def initialize(params = {}, &block)
399
+ init(::Telnet.client_config.merge(params.fetch(:options, {})))
400
+ configure_handlers(params.fetch(:handlers, []), &block)
401
+ connect
402
+ session
403
+ end
404
+
405
+ IdentiferTemplate = '#<%<class>s:0x%<id>s>'.freeze
406
+
407
+ def to_s
408
+ format(IdentiferTemplate, class: self.class.name, id: object_id.to_s(16))
409
+ end
410
+ alias inspect to_s
411
+ end
412
+ end
413
+ # module Telnet
414
+
415
+ # The Telnet module
416
+ module Telnet
417
+ # The ArgumentsParser class
418
+ class ArgumentsParser
419
+ IntegerPattern = /^\d+$/.freeze
420
+ Flags = %i[banner ssl log_level help version].freeze
421
+ attr_reader :parser, :options
422
+
423
+ def initialize(parser = OptionParser.new, options = ::Telnet.client_config.dup)
424
+ @parser = parser
425
+ @options = options
426
+ Flags.each { |method_name| method(method_name)&.call if respond_to?(method_name) }
427
+ end
428
+
429
+ def banner
430
+ @parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] <hostname> [port]"
431
+ @parser.separator ''
432
+ @parser.separator 'Options:'
433
+ end
434
+
435
+ def log_level
436
+ @parser.on_tail('-v', '--verbose', 'Increase verbosity') do
437
+ @options[:log_level] ||= 0
438
+ @options[:log_level] -= 1
439
+ end
440
+ end
441
+
442
+ def help
443
+ @parser.on_tail('-?', '--help', 'Show this message') do
444
+ puts @parser
445
+ exit
446
+ end
447
+ end
448
+
449
+ def version
450
+ @parser.on_tail('--version', 'Show version') do
451
+ puts "#{$PROGRAM_NAME} version #{Version}"
452
+ exit
453
+ end
454
+ end
455
+
456
+ def validated_port(val)
457
+ raise OptionParser::InvalidArgument, "Invalid port: #{val}" unless \
458
+ IntegerPattern.match?(val.to_s) && val.positive? && val < 65_536
459
+
460
+ val
461
+ end
462
+
463
+ def parse_positional_arguments!
464
+ @options[:host] = ARGV.shift or raise OptionParser::MissingArgument, 'hostname'
465
+ return if (given_port = ARGV.shift&.to_i).nil?
466
+
467
+ @options[:port] = @parser.validated_port(given_port).to_i
468
+ end
469
+ end
470
+ # class ArgumentsParser
471
+
472
+ def parse_arguments(arguments_parser = ArgumentsParser.new)
473
+ arguments_parser.parser.parse!(ARGV)
474
+ arguments_parser.parse_positional_arguments!
475
+ arguments_parser.options
476
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
477
+ OptionParser::MissingArgument, OptionParser::NeedlessArgument => e
478
+ puts e.message
479
+ puts parser
480
+ exit
481
+ rescue OptionParser::AmbiguousOption => e
482
+ abort e.message
483
+ end
484
+ end
485
+ # module Telnet
486
+
487
+ # The Telnet module
488
+ module Telnet
489
+ # The ConsoleHandler class
490
+ class ConsoleHandler
491
+ def message_received(ctx, message)
492
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{message}"
493
+ log.debug "Received message: #{message}"
494
+ $stdout.print message unless message.nil?
495
+ end
496
+ end
497
+ end
498
+ # module Client
499
+
500
+ # The Telnet module
501
+ module Telnet
502
+ # rubocop: disable Metrics/AbcSize
503
+ def main(args = parse_arguments)
504
+ Logging.log_level = args[:log_level]
505
+ ::Telnet::Client.new(options: args, handlers: ::Telnet::ConsoleHandler.new)
506
+ rescue Interrupt => e
507
+ warn "\n#{e.class}"
508
+ exit
509
+ rescue StandardError => e
510
+ ::Telnet::Client.log.fatal "Unexpected error: #{e.class}: #{e.message}"
511
+ e.backtrace.each { |t| log.debug t } if Logging.log_level == Logger::DEBUG
512
+ exit 1
513
+ end
514
+ # rubocop: enable Metrics/AbcSize
515
+ end
516
+ # module Telnet
517
+
518
+ Object.new.extend(::Telnet).main if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,37 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ # encoding: utf-8
4
+ # frozen_string_literal: false
5
+
6
+ # -*- mode: ruby -*-
7
+ # vi: set ft=ruby :
8
+
9
+ # =begin
10
+ #
11
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
12
+ #
13
+ # =end
14
+
15
+ require 'java'
16
+ require 'tcp-server'
17
+
18
+ require_relative 'log'
19
+ require_relative 'telnet/argument_parser'
20
+ require_relative 'telnet/server'
21
+
22
+ # The Telnet module
23
+ module Telnet
24
+ def main(args = parse_arguments)
25
+ Logging.log_level = args[:log_level]
26
+ ::Telnet::Server.new(options: args).run
27
+ rescue Interrupt => e
28
+ warn format("\r%<class>s", class: e.class)
29
+ exit
30
+ rescue StandardError => e
31
+ ::Telnet::Server.log.fatal(e.message)
32
+ abort
33
+ end
34
+ end
35
+ # module Telnet
36
+
37
+ Object.new.extend(Telnet).main if $PROGRAM_NAME == __FILE__