rcon 0.1.0 → 0.2.0
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.
- data/bin/rcontool +296 -0
- data/lib/rcon.rb +24 -17
- metadata +27 -13
data/bin/rcontool
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
################################################################
|
3
|
+
#
|
4
|
+
# rcontool - shell interface to rcon commands
|
5
|
+
#
|
6
|
+
# (C) 2006 Erik Hollensbe, License details below
|
7
|
+
#
|
8
|
+
# Use 'rcontool -h' for usage instructions.
|
9
|
+
#
|
10
|
+
# The compilation of software known as rcontool is distributed under the
|
11
|
+
# following terms:
|
12
|
+
# Copyright (C) 2005-2006 Erik Hollensbe. All rights reserved.
|
13
|
+
#
|
14
|
+
# Redistribution and use in source form, with or without
|
15
|
+
# modification, are permitted provided that the following conditions
|
16
|
+
# are met:
|
17
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
20
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
23
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
25
|
+
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
26
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
27
|
+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
28
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
29
|
+
# SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
################################################################
|
33
|
+
|
34
|
+
#
|
35
|
+
# rubygems hack
|
36
|
+
#
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'rubygems'
|
40
|
+
rescue LoadError => e
|
41
|
+
end
|
42
|
+
begin
|
43
|
+
require 'rcon'
|
44
|
+
require 'ip'
|
45
|
+
rescue LoadError => e
|
46
|
+
$stderr.puts "rcontool requires the rcon and ip libraries be installed."
|
47
|
+
$stderr.puts "You can find them both via rubygems or at http://rubyforge.org."
|
48
|
+
exit -1
|
49
|
+
end
|
50
|
+
|
51
|
+
RCONTOOL_VERSION = '0.1.0'
|
52
|
+
|
53
|
+
require 'optparse'
|
54
|
+
require 'ostruct'
|
55
|
+
|
56
|
+
#
|
57
|
+
# Manages our options
|
58
|
+
#
|
59
|
+
|
60
|
+
def get_options
|
61
|
+
options = OpenStruct.new
|
62
|
+
# ip address (IP::Address object)
|
63
|
+
options.ip_address = nil
|
64
|
+
# port (integer)
|
65
|
+
options.port = nil
|
66
|
+
# password
|
67
|
+
options.password = nil
|
68
|
+
# protocol type (one of :hlds, :source, :oldquake, :newquake)
|
69
|
+
options.protocol_type = nil
|
70
|
+
# verbose, spit out extra information
|
71
|
+
options.verbose = false
|
72
|
+
# command to execute on the server
|
73
|
+
options.command = nil
|
74
|
+
|
75
|
+
optparse = OptionParser.new do |opts|
|
76
|
+
opts.banner = "Usage: #{File.basename $0} <ip_address:port> <command> [options]"
|
77
|
+
opts.separator ""
|
78
|
+
opts.separator "Options:"
|
79
|
+
|
80
|
+
opts.on("--ip-address [ADDRESS]",
|
81
|
+
"Provide an IP address to connect to. Does not take a port.") do |ip_address|
|
82
|
+
if ! options.ip_address.nil?
|
83
|
+
$stderr.puts "Error: you have already provided an IP Address."
|
84
|
+
$stderr.puts opts
|
85
|
+
exit -1
|
86
|
+
end
|
87
|
+
|
88
|
+
options.ip_address = IP::Address.new(ip_address)
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("-r", "--port [PORT]",
|
92
|
+
"Port to connect to.") do |port|
|
93
|
+
if ! options.port.nil?
|
94
|
+
$stderr.puts "Error: you have already provided a port."
|
95
|
+
$stderr.puts opts
|
96
|
+
exit -1
|
97
|
+
end
|
98
|
+
|
99
|
+
options.port = port.to_i
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on("-c", "--command [COMMAND]",
|
103
|
+
"Command to run on the server.") do |command|
|
104
|
+
if ! options.command.nil?
|
105
|
+
$stderr.puts "Error: you have already provided a command."
|
106
|
+
$stderr.puts opts
|
107
|
+
exit -1
|
108
|
+
end
|
109
|
+
|
110
|
+
options.command = command
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.on("-p", "--password [PASSWORD]",
|
114
|
+
"Provide a password on the command line.") do |password|
|
115
|
+
options.password = password
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on("-f", "--password-from [FILENAME]",
|
119
|
+
"Get the password from a file (use '/dev/fd/0' or '/dev/stdin' to read from Standard Input).") do |filename|
|
120
|
+
if !filename.nil?
|
121
|
+
f = File.open(filename)
|
122
|
+
options.password = f.gets.chomp
|
123
|
+
f.close
|
124
|
+
else
|
125
|
+
$stderr.puts "Error: filename (from -f) is not valid."
|
126
|
+
$stderr.puts opts
|
127
|
+
exit -1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
opts.on("-t", "--protocol-type [TYPE]", [:hlds, :source, :oldquake, :newquake],
|
132
|
+
"Type of rcon connection to make: (hlds, source, oldquake, newquake).",
|
133
|
+
" Note: oldquake is quake1/quakeworld, newquake is quake2/3.") do |protocol_type|
|
134
|
+
options.protocol_type = protocol_type
|
135
|
+
end
|
136
|
+
|
137
|
+
opts.on("-v", "--[no-]verbose",
|
138
|
+
"Run verbosely, print information about each packet recieved and turnaround times.") do |verbose|
|
139
|
+
options.verbose = verbose
|
140
|
+
end
|
141
|
+
|
142
|
+
opts.on("-h", "--help",
|
143
|
+
"This help message.") do
|
144
|
+
$stderr.puts opts
|
145
|
+
exit -1
|
146
|
+
end
|
147
|
+
|
148
|
+
opts.on("--version", "Print the version information.") do
|
149
|
+
$stderr.puts "This is rcontool version #{RCONTOOL_VERSION},"
|
150
|
+
$stderr.puts "it is located at #{File.expand_path $0}."
|
151
|
+
exit -1
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.separator ""
|
155
|
+
opts.separator "Note: IP, port, protocol type, password and command are required to function."
|
156
|
+
opts.separator ""
|
157
|
+
opts.separator "Examples (all are equivalent):"
|
158
|
+
opts.separator "\t#{File.basename($0)} 10.0.0.11 status -t hlds -r 27015 -p foobar"
|
159
|
+
opts.separator "\techo 'foobar' | #{File.basename($0)} 10.0.0.11:27015 status -t hlds -f /dev/stdin"
|
160
|
+
opts.separator "\t#{File.basename($0)} --ip-address 10.0.0.11 --port 27015 -c status -t hlds -f file_with_password"
|
161
|
+
opts.separator ""
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
################################################################
|
166
|
+
#
|
167
|
+
# This hackery is to help facilitate the bareword options if
|
168
|
+
# they exist, while still allowing for the option parser
|
169
|
+
# to work properly.
|
170
|
+
#
|
171
|
+
################################################################
|
172
|
+
|
173
|
+
s1 = ARGV.shift
|
174
|
+
s2 = ARGV.shift
|
175
|
+
|
176
|
+
begin
|
177
|
+
options.ip_address = IP::Address.new(s1)
|
178
|
+
options.command = s2
|
179
|
+
rescue IP::AddressException => e
|
180
|
+
# attempt to split it first... not sure how to best handle this situation
|
181
|
+
begin
|
182
|
+
ip,port = s1.split(/:/, 2)
|
183
|
+
options.ip_address = IP::Address.new(ip)
|
184
|
+
options.port = port.to_i
|
185
|
+
options.command = s2
|
186
|
+
rescue Exception => e
|
187
|
+
end
|
188
|
+
|
189
|
+
if [options.ip_address, options.port].include? nil
|
190
|
+
ARGV.unshift(s2)
|
191
|
+
ARGV.unshift(s1)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
optparse.parse!
|
196
|
+
|
197
|
+
if [options.ip_address, options.protocol_type, options.port, options.password, options.command].include? nil
|
198
|
+
$stderr.puts optparse
|
199
|
+
exit -1
|
200
|
+
end
|
201
|
+
|
202
|
+
return options
|
203
|
+
end
|
204
|
+
|
205
|
+
def verbose(string)
|
206
|
+
$stderr.puts string if $options.verbose
|
207
|
+
end
|
208
|
+
|
209
|
+
def dump_source_packet(packet)
|
210
|
+
if $options.verbose
|
211
|
+
verbose "Request ID: #{packet.request_id}"
|
212
|
+
verbose "Packet Size: #{packet.packet_size}"
|
213
|
+
verbose "Response Type: #{packet.command_type}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
################################################################
|
218
|
+
#
|
219
|
+
# start main block
|
220
|
+
#
|
221
|
+
################################################################
|
222
|
+
|
223
|
+
$options = get_options
|
224
|
+
|
225
|
+
################################################################
|
226
|
+
#
|
227
|
+
# Source query
|
228
|
+
#
|
229
|
+
################################################################
|
230
|
+
|
231
|
+
if $options.protocol_type == :source
|
232
|
+
verbose "Protocol type 'SOURCE' selected."
|
233
|
+
|
234
|
+
rcon = RCon::Query::Source.new($options.ip_address.ip_address, $options.port)
|
235
|
+
|
236
|
+
# if we have a verbose request, give all the information we can about
|
237
|
+
# the query, including the packet information.
|
238
|
+
rcon.return_packets = $options.verbose
|
239
|
+
|
240
|
+
verbose "Attempting authentication to #{$options.ip_address.ip_address}:#{$options.port} with password '#{$options.password}'"
|
241
|
+
|
242
|
+
value = rcon.auth $options.password
|
243
|
+
|
244
|
+
dump_source_packet value
|
245
|
+
|
246
|
+
if ($options.verbose && value.command_type == RCon::Packet::Source::RESPONSE_AUTH) || value
|
247
|
+
verbose "Authentication succeeded. Sending command: '#{$options.command}'"
|
248
|
+
|
249
|
+
value = rcon.command $options.command
|
250
|
+
|
251
|
+
dump_source_packet value
|
252
|
+
verbose ""
|
253
|
+
|
254
|
+
if $options.verbose
|
255
|
+
puts value.string1
|
256
|
+
else
|
257
|
+
puts value
|
258
|
+
end
|
259
|
+
|
260
|
+
exit 0
|
261
|
+
else
|
262
|
+
$stderr.puts "Authentication failed."
|
263
|
+
exit 1
|
264
|
+
end
|
265
|
+
|
266
|
+
################################################################
|
267
|
+
#
|
268
|
+
# Original Query
|
269
|
+
#
|
270
|
+
################################################################
|
271
|
+
|
272
|
+
else
|
273
|
+
rcon = nil
|
274
|
+
case $options.protocol_type
|
275
|
+
when :hlds
|
276
|
+
verbose "Protocol type 'HLDS' selected"
|
277
|
+
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
278
|
+
RCon::Query::Original::HLDS)
|
279
|
+
when :oldquake
|
280
|
+
verbose "Protocol type 'OLDQUAKE' selected"
|
281
|
+
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
282
|
+
RCon::Query::Original::QUAKEWORLD)
|
283
|
+
when :newquake
|
284
|
+
verbose "Protocol type 'NEWQUAKE' selected"
|
285
|
+
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
286
|
+
RCon::Query::Original::NEWQUAKE)
|
287
|
+
end
|
288
|
+
verbose "Attempting transmission to #{$options.ip_address.ip_address}:#{$options.port}"
|
289
|
+
verbose "Using password: '#{$options.password}' and sending command: '#{$options.command}'"
|
290
|
+
verbose ""
|
291
|
+
string = rcon.command($options.command)
|
292
|
+
|
293
|
+
puts string
|
294
|
+
exit 0
|
295
|
+
end
|
296
|
+
|
data/lib/rcon.rb
CHANGED
@@ -4,7 +4,7 @@ require 'socket'
|
|
4
4
|
# RCon is a module to work with Quake 1/2/3, Half-Life, and Half-Life
|
5
5
|
# 2 (Source Engine) RCon (Remote Console) protocols.
|
6
6
|
#
|
7
|
-
# Version:: 0.
|
7
|
+
# Version:: 0.2.0
|
8
8
|
#
|
9
9
|
# Author:: Erik Hollensbe <erik@hollensbe.org>
|
10
10
|
#
|
@@ -20,9 +20,13 @@ require 'socket'
|
|
20
20
|
# Usage is fairly simple:
|
21
21
|
#
|
22
22
|
# # Note: Other classes have different constructors
|
23
|
+
#
|
23
24
|
# rcon = RCon::Query::Source.new("10.0.0.1", 27015)
|
25
|
+
#
|
24
26
|
# rcon.auth("foobar") # source only
|
27
|
+
#
|
25
28
|
# rcon.command("mp_friendlyfire") => "mp_friendlyfire = 1"
|
29
|
+
#
|
26
30
|
# rcon.cvar("mp_friendlyfire") => 1
|
27
31
|
#
|
28
32
|
# ================================================================
|
@@ -34,8 +38,7 @@ require 'socket'
|
|
34
38
|
# Redistribution and use in source form, with or without
|
35
39
|
# modification, are permitted provided that the following conditions
|
36
40
|
# are met:
|
37
|
-
# 1. Redistributions of source code must retain the above copyright
|
38
|
-
# notice, this list of conditions and the following disclaimer.
|
41
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
39
42
|
#
|
40
43
|
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
41
44
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
@@ -104,8 +107,8 @@ class RCon::Packet::Source
|
|
104
107
|
attr_accessor :packet_size
|
105
108
|
# Request Identifier, used in managing multiple requests at once
|
106
109
|
attr_accessor :request_id
|
107
|
-
# Type of command, normally COMMAND_AUTH or COMMAND_EXEC
|
108
|
-
attr_accessor :
|
110
|
+
# Type of command, normally COMMAND_AUTH or COMMAND_EXEC. In response packets, RESPONSE_AUTH or RESPONSE_NORM
|
111
|
+
attr_accessor :command_type
|
109
112
|
# First string, the only used one in the protocol, contains
|
110
113
|
# commands and responses. Null terminated.
|
111
114
|
attr_accessor :string1
|
@@ -121,7 +124,7 @@ class RCon::Packet::Source
|
|
121
124
|
@request_id = rand(1000)
|
122
125
|
@string1 = string
|
123
126
|
@string2 = TRAILER
|
124
|
-
@
|
127
|
+
@command_type = COMMAND_EXEC
|
125
128
|
|
126
129
|
@packet_size = build_packet.length
|
127
130
|
|
@@ -137,7 +140,7 @@ class RCon::Packet::Source
|
|
137
140
|
@request_id = rand(1000)
|
138
141
|
@string1 = string
|
139
142
|
@string2 = TRAILER
|
140
|
-
@
|
143
|
+
@command_type = COMMAND_AUTH
|
141
144
|
|
142
145
|
@packet_size = build_packet.length
|
143
146
|
|
@@ -150,7 +153,7 @@ class RCon::Packet::Source
|
|
150
153
|
# that srcds actually needs.
|
151
154
|
#
|
152
155
|
def build_packet
|
153
|
-
return [@request_id, @
|
156
|
+
return [@request_id, @command_type, @string1, @string2].pack("VVa#{@string1.length}a2")
|
154
157
|
end
|
155
158
|
|
156
159
|
# Returns a string representation of the packet, useful for
|
@@ -197,6 +200,8 @@ class RCon::Query::Original < RCon::Query
|
|
197
200
|
attr_reader :port
|
198
201
|
# RCon password
|
199
202
|
attr_reader :password
|
203
|
+
# type of server
|
204
|
+
attr_reader :server_type
|
200
205
|
|
201
206
|
#
|
202
207
|
# Creates a RCon::Query::Original object for use.
|
@@ -205,7 +210,9 @@ class RCon::Query::Original < RCon::Query
|
|
205
210
|
# values:
|
206
211
|
#
|
207
212
|
# HLDS - Half Life 1 (will not work with older versions of HLDS)
|
213
|
+
#
|
208
214
|
# QUAKEWORLD - QuakeWorld/Quake 1
|
215
|
+
#
|
209
216
|
# NEWQUAKE - Quake 2/3 (and many derivatives)
|
210
217
|
#
|
211
218
|
|
@@ -213,7 +220,7 @@ class RCon::Query::Original < RCon::Query
|
|
213
220
|
@host = host
|
214
221
|
@port = port
|
215
222
|
@password = password
|
216
|
-
@
|
223
|
+
@server_type = type
|
217
224
|
end
|
218
225
|
|
219
226
|
#
|
@@ -241,7 +248,7 @@ class RCon::Query::Original < RCon::Query
|
|
241
248
|
@socket.print "\xFF" * 4 + "rcon #{@challenge_id} \"#{@password}\" #{@request}\n\x00"
|
242
249
|
@response = retrieve_socket_data
|
243
250
|
|
244
|
-
@response.sub! /^\xFF\xFF\xFF\xFF#{@
|
251
|
+
@response.sub! /^\xFF\xFF\xFF\xFF#{@server_type}/, ""
|
245
252
|
@response.sub! /\x00+$/, ""
|
246
253
|
|
247
254
|
return @response
|
@@ -316,7 +323,7 @@ class RCon::Query::Source < RCon::Query
|
|
316
323
|
# Authentication Status
|
317
324
|
attr_reader :authed
|
318
325
|
# return full packet, or just data?
|
319
|
-
|
326
|
+
attr_accessor :return_packets
|
320
327
|
|
321
328
|
#
|
322
329
|
# Given a host and a port (dotted-quad or hostname OK), creates
|
@@ -367,8 +374,8 @@ class RCon::Query::Source < RCon::Query
|
|
367
374
|
@socket.print @packet.to_s
|
368
375
|
rpacket = build_response_packet
|
369
376
|
|
370
|
-
if rpacket.
|
371
|
-
raise RCon::NetworkException.new("error sending command: #{rpacket.
|
377
|
+
if rpacket.command_type != RCon::Packet::Source::RESPONSE_NORM
|
378
|
+
raise RCon::NetworkException.new("error sending command: #{rpacket.command_type}")
|
372
379
|
end
|
373
380
|
|
374
381
|
if @return_packets
|
@@ -398,12 +405,12 @@ class RCon::Query::Source < RCon::Query
|
|
398
405
|
rpacket = nil
|
399
406
|
2.times { rpacket = build_response_packet }
|
400
407
|
|
401
|
-
if rpacket.
|
402
|
-
raise RCon::NetworkException.new("error authenticating: #{rpacket.
|
408
|
+
if rpacket.command_type != RCon::Packet::Source::RESPONSE_AUTH
|
409
|
+
raise RCon::NetworkException.new("error authenticating: #{rpacket.command_type}")
|
403
410
|
end
|
404
411
|
|
405
412
|
@authed = true
|
406
|
-
if @
|
413
|
+
if @return_packets
|
407
414
|
return rpacket
|
408
415
|
else
|
409
416
|
return true
|
@@ -463,7 +470,7 @@ class RCon::Query::Source < RCon::Query
|
|
463
470
|
|
464
471
|
rpacket.packet_size = total_size
|
465
472
|
rpacket.request_id = request_id
|
466
|
-
rpacket.
|
473
|
+
rpacket.command_type = type
|
467
474
|
|
468
475
|
# strip nulls (this is actually the end of string1 and string2)
|
469
476
|
rpacket.string1 = response.sub /\x00\x00$/, ""
|
metadata
CHANGED
@@ -3,12 +3,11 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rcon
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-
|
8
|
-
summary:
|
9
|
-
console)"
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2006-02-08 00:00:00 -08:00
|
8
|
+
summary: Ruby class to work with Quake 1/2/3, Half-Life and Source Engine rcon (remote console)
|
10
9
|
require_paths:
|
11
|
-
|
10
|
+
- lib
|
12
11
|
email: erik@hollensbe.org
|
13
12
|
homepage:
|
14
13
|
rubyforge_project:
|
@@ -19,22 +18,37 @@ bindir: bin
|
|
19
18
|
has_rdoc: true
|
20
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
20
|
requirements:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
version: 0.0.0
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
26
24
|
version:
|
27
25
|
platform: ruby
|
28
26
|
signing_key:
|
29
27
|
cert_chain:
|
30
28
|
authors:
|
31
|
-
|
29
|
+
- Erik Hollensbe
|
32
30
|
files:
|
33
|
-
|
31
|
+
- lib/rcon.rb
|
32
|
+
- bin/rcontool
|
34
33
|
test_files: []
|
34
|
+
|
35
35
|
rdoc_options: []
|
36
|
+
|
36
37
|
extra_rdoc_files: []
|
37
|
-
|
38
|
+
|
39
|
+
executables:
|
40
|
+
- rcontool
|
38
41
|
extensions: []
|
42
|
+
|
39
43
|
requirements: []
|
40
|
-
|
44
|
+
|
45
|
+
dependencies:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ip
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.1.1
|
54
|
+
version:
|