cmdserver 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fae792cda07478c097e01d974c9753ad2a79e1db
4
- data.tar.gz: d881017e9579698f4d014df4f553f85146aa8df6
3
+ metadata.gz: 2dfb54b6f9772f32e6e92b953e278ff455fcabc5
4
+ data.tar.gz: f40b0e2b2372a0f9fd39740fcc9463d2c3bf70cc
5
5
  SHA512:
6
- metadata.gz: 67f8c992d4be5fddcf46fea9649b967fc389f99f5635332d764ecf4223920309deff0335ac53a952c511753be12bbb706f4cfa119eb575a2b1b54fee21e8a7bf
7
- data.tar.gz: fa8335ab0c42b8f3fe8c9c5ac83c76824dcafe1a7f5044859ded9305913d5ab1e0a8cd01c3a9f5fe07763f5c74d7a9af83c5b439a41733533c46f5dc05a88a5f
6
+ metadata.gz: 76583b63b095b3a146823e9245ebdceb525c7ae84c7a4fc0d3f0b0660522013b59b9c7d56dff3f0de41cf69e71e2237b44cec3f7f5d5e8db1b279d0bdaf4af1b
7
+ data.tar.gz: 3ea121d0fad232fbcd0e0185495fe4f0a7bf38fd0b51f53c36151df3b9fcde6c5aaa0ee7088fadaddb6c1ab5912806d8a3993e2a31d23a48ea17fbc5ed499953
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  *.swp
11
+ tags
data/README.md CHANGED
@@ -1,8 +1,15 @@
1
1
  # Cmdserver
2
2
 
3
3
  Cmdserver gives you the ability to design a very simple command server.
4
- Simply create a .rb module under `~/.cmdserver/modules/` and override the module `Cmdserver::CmdProtocol`.
5
- Fire up your server and you are done! It is ready to respond to the commands you have defined.
4
+ Simply create a .rb module under `~/.cmdserver/modules/` and override the module `Cmdserver::Cmdprotocol`.
5
+ Fire up your server and you are done! It is ready to respond to the commands you have defined.
6
+
7
+ From version 1.0.0 the `cmdserver` executable is provided for even faster development startup. Please read the __Using 'cmdserver'__ section to learn more.
8
+
9
+ ## Future plans
10
+
11
+ Currently, the server requests and responses are being sent in plaintext, which isn't exaclty the best idea. However, The server isn't ment as a substitute for SSH or other RCE tools. That is the reason why I haven't included support for SSL. It should be possible to hack this into your server through the modules, but it might be a bit painfull to do so (I haven't tried honestly). So, if there is enough requests to add this in, I might start working on it.
12
+ Besides that, there aren't any more future plans besides necessary bugfixes, documentation and examples.
6
13
 
7
14
  ## Installation
8
15
 
@@ -20,15 +27,56 @@ Or install it yourself as:
20
27
 
21
28
  $ gem install cmdserver
22
29
 
23
- ## Usage
30
+ ## Using the 'cmdserver' executable
31
+
32
+ With version 1.0.0, you can now easily create a module by issuing `cmdserver --skel > ~/.cmdserver/modules/test.rb`. This will generate
33
+ a skeleton file which will be loaded by the server during startup. Within the skeleton file there is a quick and dirty manual of
34
+ what you should do in order to get started. If the `~/.cmdserver/modules` path doesn't exits, create it. Otherwise it will be created the first time the executable is started.
35
+ To start the server, just type `cmdserver -p PORTNUMBER` where PORTNUMBER is the port you wish your server to listen at.
36
+ The skeleton file already has the `extension` command defined. To test if everything is working as it should, connect to your server via `telnet` or `netcat` through the specified port, and send it the command string `extension`.
37
+ The server should respond with `Command recieved`.
38
+
39
+ To daemonize the process supply the -d switch.
40
+ Here is an example session:
41
+
42
+ ```
43
+ $> cmdserver -dp 2121
44
+ Loading module: /home/user/.cmdserver/modules/test.rb
45
+ Forking server to the background...
46
+ $> nc localhost 2121
47
+ extension
48
+ Command recieved
49
+ ^C
50
+ $>
51
+ ```
52
+
53
+ You can also force the server to reload all modules by sending it the SIGHUP signal.
54
+
55
+ Other switches that are available:
56
+ ```
57
+ $> cmdserver --help
58
+ Usage: cmdserver [options]
59
+ -d, --daemon Daemonize process
60
+ -p, --port PORTNUM Specify arbitrary port
61
+ -w, --workdir WORKDIR Active working directory. '~/.cmdserver/' by default.
62
+ Without -m specified, module directory is set to 'WORKDIR/modules'.
63
+ This directory is also where 'cmdserver.log' gets created.
64
+ -m, --module-dir MODULE_DIR Load modules from MODULE_DIR instead of '~/.cmdserver/modules'
65
+ --skel Dumps a simple module template to standard output.
66
+ --debug Turn on debugging information.
67
+ -h, --help Display this help message
68
+ -v, --version Display version information and exit
69
+ ```
70
+
71
+ ## Using the programming API
24
72
 
25
73
  By default, Cmdserver looks into `~/.cmdserver/modules` for any .rb files present. It then `require`-s them into the program.
26
- In these .rb files, you override the module `Cmdserver::CmdProtocol` as demonstrated bellow
74
+ In these .rb files, you override the module `Cmdserver::Cmdprotocol` as demonstrated bellow
27
75
 
28
76
  ```ruby
29
- module Cmdserver::CmdProtocol
77
+ module Cmdserver::Cmdprotocol
30
78
  def self.extend_protocol
31
- @protocol_hash["CustomCommand"] = -> client_socket, arguments { client_socket.puts "You sent: #{arguments}" }
79
+ @protocol["CustomCommand"] = -> client_socket, arguments { client_socket.puts "You sent: #{arguments}" }
32
80
  end
33
81
 
34
82
  def self.default_action(client_socket, arguments)
@@ -44,7 +92,7 @@ For now, argument parsing is left up to the individual functions.
44
92
 
45
93
  Also note that overriding the default behaviour can be done only once. The last loaded module that redefines `self.default_action` is what is going to happen, when the command is not recognized. By default, it echoes back whatever it recieves.
46
94
 
47
- The `@protocol_hash` can be destroid in any module. The hash gets copied into the core on a per-module basis. Note that this can introduce
95
+ The `@protocol` can be destroyed in any module. The hash gets copied into the core on a per-module basis. Note that this can introduce
48
96
  conflicts when many modules define the same keys for commands.
49
97
 
50
98
 
data/cmdserver.gemspec CHANGED
@@ -10,8 +10,17 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["gordon.zar@gmail.com"]
11
11
 
12
12
  spec.summary = %q{Simple, module based command execution server over tcp.}
13
- #spec.description = %q{TODO: Write a longer description or delete this line.}
14
- spec.homepage = "https://github.com/majorendian/Modular-Tcp-Command-Server"
13
+ spec.description = %q{
14
+ Note: Version 1.0.0 introduces some breaking changes. Make sure to read the documentation in case something is breaking
15
+ at your end.
16
+
17
+ 'cmdserver' is a simple, multi-threaded, module based and extensible command server with a straight forward API.
18
+ Much of the functionality relies on ruby's ability to override module definitions at runtime. This, in turn
19
+ becomes the servers protocol. Read the documentation further details.
20
+
21
+ Gem includes an executable launching the server with a simple template file for getting started with writting protocol extension modules.
22
+ }
23
+ spec.homepage = "https://github.com/majorendian/cmdserver"
15
24
  spec.license = "MIT"
16
25
 
17
26
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
@@ -24,10 +33,10 @@ Gem::Specification.new do |spec|
24
33
 
25
34
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
35
  spec.bindir = "release"
27
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.executables = spec.files.grep(%r{^release/}) { |f| File.basename(f) }
28
37
  spec.require_paths = ["lib"]
29
38
 
30
- spec.add_development_dependency "bundler", "~> 1.10"
31
- spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "bundler", ">= 1.7"
40
+ spec.add_development_dependency "rake", ">= 10.0"
32
41
  spec.add_development_dependency "rspec"
33
42
  end
data/lib/cmdserver.rb CHANGED
@@ -1,70 +1,53 @@
1
1
  require "socket"
2
2
  require "pathname"
3
3
  require "cmdserver/version"
4
+ require "cmdserver/cmdprotocol"
5
+ require "cmdserver/templates"
6
+ require "cmdserver/cli"
4
7
 
5
8
  module Cmdserver
6
9
 
7
- module CmdProtocol
8
- # Protocol stuff goes here.
9
- # Should be loaded from ~/.cmdserver/modules/*.rb
10
- @protocol_hash = {}
11
- @protocol = @protocol_hash
12
- def self.extend_protocol()
13
- @protocol = {
14
- "dummy" => -> cs, args { cs.puts "Dummy reply"}
15
- }
16
- @protocol_hash = @protocol
17
- end
18
-
19
- def self.default_action(cs, args)
20
- cs.puts args
21
- end
22
-
23
- module_function
24
- def get_protocol_hash()
25
- self.extend_protocol()
26
- @protocol = @protocol_hash
27
- return @protocol
28
- end
29
-
30
- # Default behaviour when querry was not found
31
- module_function
32
- def default(cs, args)
33
- # NOTE: args is a String
34
- self.default_action(cs, args)
35
- end
36
- end
10
+ class Settings# {{{
37
11
 
38
- class Settings
12
+ attr_reader :module_dir
13
+ attr_reader :workdir
39
14
 
40
- def initialize(config_dir="~/.cmdserver/")
41
- @workdir = Pathname.new(File.expand_path(config_dir))
42
- @config_rc = @workdir + "config" # Configuration file currently unused
15
+ def initialize(work_dir="~/.cmdserver/")
16
+ @workdir = Pathname.new(File.expand_path(work_dir))
43
17
  @module_dir = @workdir + "modules"
44
18
  if not @workdir.exist?
45
19
  Dir.mkdir @workdir
46
- if not @config_rc.exist?
47
- File.new @config_rc, "w"
48
- end
49
20
  end
50
21
  if not @module_dir.exist?
51
22
  Dir.mkdir @module_dir
52
23
  end
53
- # Load modules contained within the module
54
- # directories
55
- load_modules()
24
+ # load_modules is now being called by the
25
+ # TCPCommandServer class
56
26
  end
57
27
 
58
28
  def load_modules
59
- Dir.glob("#{@module_dir}/*.rb").each do |mod|
60
- CmdProtocol.extend_protocol()
29
+ Dir.glob("#{@module_dir}/**/*.rb").each do |mod|
30
+ Cmdserver::Cmdprotocol.extend_protocol()
61
31
  puts "Loading module: #{mod}"
62
32
  require mod
63
33
  end
64
34
  end
65
- end
35
+ end# }}}
36
+
37
+ class CustomSettings < Settings# {{{
66
38
 
67
- class Command # Class provided for the possibilty of future extensibility
39
+ def initialize(options)
40
+ @workdir = options[:workdir]
41
+ if not @workdir.nil?
42
+ super(work_dir=@workdir)
43
+ else
44
+ super()
45
+ end
46
+ @module_dir = options[:module_dir] if options[:module_dir]
47
+ end
48
+ end# }}}
49
+
50
+ class Command # Class provided for the possibilty of future extensibility# {{{
68
51
  def initialize
69
52
  end
70
53
 
@@ -72,46 +55,87 @@ module Cmdserver
72
55
  client_socket.puts "Dummy command call"
73
56
  end
74
57
 
75
- end
58
+ end# }}}
76
59
 
77
- class TCPCommandServer
60
+ class TCPCommandServer# {{{
78
61
 
79
62
  attr_accessor :socket
63
+ attr_reader :settings
64
+ attr_writer :reload_settings
80
65
 
81
66
  def initialize(port, hash={}, settings=nil, debug=false)
82
67
  @socket = TCPServer.new(port)
68
+ @reload_settings = false # Used from Signal.trap to allow module reloading
83
69
  @cmd_hash = hash # hash of commands
84
- load_cmd_proto()
70
+ @settings = settings
85
71
  @debug = debug
86
- if settings.nil?
72
+ if @settings.nil?
87
73
  @settings = Settings.new()
88
74
  end
75
+ @settings.load_modules()
76
+ load_cmd_proto()
89
77
  end
90
78
 
91
79
  def load_cmd_proto()
92
- phash = CmdProtocol.get_protocol_hash()
80
+ phash = Cmdserver::Cmdprotocol.get_protocol_hash()
93
81
  phash.each_key do |key|
94
82
  @cmd_hash[key] = phash[key]
95
83
  end
84
+ puts phash if @debug
96
85
  end
97
86
 
98
87
  def registerCallback(string, aproc)
99
88
  @cmd_hash[string] = aproc
100
89
  end
101
90
 
91
+
92
+ # "Private" schelued updater.
93
+ # Allows for loading of modules
94
+ # without having to restart the server
95
+ def _update_loop_
96
+ loop do
97
+ sleep 2
98
+ _update_()
99
+ end
100
+ end
101
+
102
+ def _update_
103
+ if @reload_settings
104
+ @settings.load_modules()
105
+ @reload_settings = false
106
+ end
107
+ end
108
+
109
+ # Start the local updater,
110
+ # and the socket => thread loop
102
111
  def start()
112
+ # Start the local updater
113
+ Thread.new{ _update_loop_() }
103
114
  loop do
104
- client = @socket.accept
115
+ begin
116
+ client = @socket.accept
117
+ # update so that clients get the updated module
118
+ # prevents race condition between connection
119
+ # and the updater loop
120
+ # Thanks to the variable, the modules will not get
121
+ # loaded twice in a single update
122
+ _update_()
123
+ rescue SystemCallError
124
+ exit -1
125
+ end
126
+
105
127
  Thread.new{ process_client(client) }
106
128
  end
107
129
  end
108
130
 
109
- def process_client(client)
131
+ def process_client(client)# {{{
110
132
  #NOTE: This should remain as an isolated thread process
111
133
  loop do
112
134
  request = client.gets
135
+ # TODO:
136
+ # request = Cmdserver::Connection.client_to_server(request)
113
137
  if not request.nil?
114
- request.chomp!
138
+ request.chomp!
115
139
  puts "Got '#{request}'" if @debug
116
140
  real_key = nil
117
141
  @cmd_hash.each_key do |key|
@@ -120,7 +144,7 @@ module Cmdserver
120
144
  end
121
145
  end
122
146
  puts "real_key:#{real_key}" if @debug
123
- if not real_key.nil?
147
+ if not real_key.nil? and request.start_with? real_key
124
148
  begin
125
149
  request.sub! real_key, ""
126
150
  request.lstrip!
@@ -134,17 +158,13 @@ module Cmdserver
134
158
  raise $!
135
159
  end
136
160
  else
137
- Cmdserver::CmdProtocol.default(client, request)
161
+ Cmdserver::Cmdprotocol.default(client, request)
138
162
  end
139
163
  else
140
164
  client.close()
141
165
  break
142
166
  end
143
167
  end
144
- end
145
- end
168
+ end# }}}
169
+ end# }}}
146
170
  end
147
-
148
-
149
- #server = TCPCommandServer.new(2121, {}, false)
150
- #server.start()
@@ -0,0 +1,71 @@
1
+ module Cmdserver::CLI
2
+
3
+ DAEMON_NAME = "cmdserver"
4
+ DAEMON_LOGFILE = "cmdserver.log"
5
+
6
+ class Daemonizer
7
+ def initialize()
8
+ @dprocess = nil
9
+ @daemon_pid_file = nil
10
+ @daemon_pid = nil
11
+ @daemon_running = false
12
+ end
13
+
14
+ def daemonize()
15
+ puts "Forking server to the background..."
16
+
17
+ if @dprocess.nil?
18
+ puts "FATAL ERROR: No daemon process specified!"
19
+ exit -1
20
+ end
21
+
22
+ pid = fork
23
+ if not pid
24
+ #child
25
+ daemon_preparation()
26
+ @dprocess.call()
27
+ puts "Server started."
28
+ end
29
+ return pid
30
+ end
31
+
32
+ def daemon_preparation
33
+ $0 = DAEMON_NAME
34
+ Process.setsid
35
+ Signal.trap("HUP", proc { _handle_sighup } )
36
+ Signal.trap("TERM", proc { _handle_sigterm } )
37
+ end
38
+
39
+ def _handle_sighup()
40
+ # Placeholder
41
+ end
42
+
43
+ def _handle_sigterm()
44
+ # Placeholder
45
+ end
46
+ end
47
+
48
+ class ServerDaemonizer < Daemonizer
49
+ def initialize(server)
50
+ super()
51
+ @server = server
52
+ @dprocess = -> { server.start() }
53
+ end
54
+
55
+ def daemon_preparation
56
+ super()
57
+ logpath = Pathname.new(@server.settings.workdir) + Pathname.new(DAEMON_LOGFILE)
58
+ $stdout.reopen(logpath, "a")
59
+ $stderr = $stdout
60
+ end
61
+
62
+ # NOTE: Could use some logging mechanism...
63
+ def _handle_sighup()
64
+ @server.reload_settings = true
65
+ end
66
+
67
+ def _handle_sigterm()
68
+ @server.socket.close()
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ module Cmdserver::Cmdprotocol
2
+ # Protocol stuff goes here.
3
+ # Should be loaded from ~/.cmdserver/modules/*.rb
4
+ @protocol = {}
5
+
6
+ module_function
7
+ def extend_protocol()
8
+ @protocol = {
9
+ "dummy" => -> cs, args { cs.puts "Dummy reply"}
10
+ }
11
+ end
12
+
13
+ def default_action(cs, args)
14
+ cs.puts args
15
+ end
16
+
17
+ def get_protocol_hash()
18
+ extend_protocol()
19
+ return @protocol
20
+ end
21
+
22
+ # Default behaviour when querry was not found
23
+ def default(cs, args)
24
+ # NOTE: args is a String
25
+ default_action(cs, args)
26
+ end
27
+
28
+ end
@@ -0,0 +1,25 @@
1
+ module Cmdserver::Templates
2
+ class BasicTemplate
3
+
4
+ attr_reader :body
5
+
6
+ def initialize
7
+ @body = <<TEMPLATE
8
+ module Cmdserver::Cmdprotocol
9
+ def self.extend_protocol()
10
+ # Replace the bellow with your own commands.
11
+ # Each key is a command your server will accept and
12
+ # call the Proc or Command associated with it.
13
+ # 'Command' can also be a class as long as it has a 'call' method
14
+ @protocol["extension"] = -> client_socket, argument { client_socket.puts "Command recieved" }
15
+ end
16
+
17
+ # Replace the bellow with your own default 'command not found' action
18
+ def self.default_action(client_socket, argument)
19
+ client_socket.puts "No such command: '\#{argument}'"
20
+ end
21
+ end
22
+ TEMPLATE
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Cmdserver
2
- VERSION = "0.10.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/release/cmdserver ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "bundler/setup"
5
+ require "cmdserver"
6
+
7
+ options = {
8
+ :debug => false,
9
+ }
10
+ optparse = OptionParser.new do |opts|
11
+ opts.banner = <<"HELP"
12
+ Usage: cmdserver [options]
13
+ HELP
14
+
15
+ opts.on("-d", "--daemon", "Daemonize process") do
16
+ options[:daemon] = true
17
+ end
18
+
19
+ opts.on("-p", "--port PORTNUM", "Specify arbitrary port") do |portarg|
20
+ options[:port] = portarg
21
+ end
22
+
23
+ opts.on("-w", "--workdir WORKDIR", "Active working directory. '~/.cmdserver/' by default.\n\t\t\t\t\tWithout -m specified, module directory is set to 'WORKDIR/modules'.\n\t\t\t\t\tThis directory is also where 'cmdserver.log' gets created.") do |workdir|
24
+ options[:workdir] = workdir
25
+ end
26
+
27
+ opts.on("-m", "--module-dir MODULE_DIR", "Load modules from MODULE_DIR instead of '~/.cmdserver/modules'" ) do |module_dir|
28
+ options[:module_dir] = module_dir
29
+ end
30
+
31
+ opts.on("--skel", "Dumps a simple module template to standard output.") do
32
+ puts Cmdserver::Templates::BasicTemplate.new().body
33
+ exit
34
+ end
35
+
36
+ opts.on("--debug", "Turn on debugging information.") do
37
+ options[:debug] = true
38
+ end
39
+
40
+ opts.on("-h", "--help", "Display this help message") do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on("-v", "--version", "Display version information and exit") do
46
+ puts Cmdserver::VERSION
47
+ exit
48
+ end
49
+ end
50
+
51
+ optparse.parse!
52
+
53
+
54
+ custom_settings = Cmdserver::CustomSettings.new(options)
55
+ port = options[:port]
56
+ if port.nil?
57
+ puts "Port required.\nSee --help"
58
+ exit(1)
59
+ end
60
+ server = Cmdserver::TCPCommandServer.new(port, {}, settings=custom_settings, debug=options[:debug])
61
+
62
+ begin
63
+ if options[:daemon]
64
+ daem = Cmdserver::CLI::ServerDaemonizer.new(server)
65
+ daem.daemonize()
66
+ else
67
+ server.start()
68
+ end
69
+ rescue Interrupt => ie
70
+ puts
71
+ puts "Terminating..."
72
+ server.socket.close()
73
+ exit
74
+ end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdserver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernest Deák
8
8
  autorequire:
9
9
  bindir: release
10
10
  cert_chain: []
11
- date: 2015-11-01 00:00:00.000000000 Z
11
+ date: 2015-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '1.7'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
@@ -52,10 +52,18 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description:
55
+ description: "\n Note: Version 1.0.0 introduces some breaking changes. Make sure
56
+ to read the documentation in case something is breaking\n at your end.\n\n 'cmdserver'
57
+ is a simple, multi-threaded, module based and extensible command server with a straight
58
+ forward API.\n Much of the functionality relies on ruby's ability to override
59
+ module definitions at runtime. This, in turn\n becomes the servers protocol.
60
+ Read the documentation further details.\n\n Gem includes an executable launching
61
+ the server with a simple template file for getting started with writting protocol
62
+ extension modules.\n "
56
63
  email:
57
64
  - gordon.zar@gmail.com
58
- executables: []
65
+ executables:
66
+ - cmdserver
59
67
  extensions: []
60
68
  extra_rdoc_files: []
61
69
  files:
@@ -71,8 +79,12 @@ files:
71
79
  - bin/testserver
72
80
  - cmdserver.gemspec
73
81
  - lib/cmdserver.rb
82
+ - lib/cmdserver/cli.rb
83
+ - lib/cmdserver/cmdprotocol.rb
84
+ - lib/cmdserver/templates.rb
74
85
  - lib/cmdserver/version.rb
75
- homepage: https://github.com/majorendian/Modular-Tcp-Command-Server
86
+ - release/cmdserver
87
+ homepage: https://github.com/majorendian/cmdserver
76
88
  licenses:
77
89
  - MIT
78
90
  metadata: