telnet-server 1.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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__