rcon 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|