mirror of
https://github.com/ENSL/ensl.org.git
synced 2024-11-15 01:11:23 +00:00
Merge pull request #18 from cblanc/cleanup_server
Attempt to remove RCON and clean up legacy code from Server Model
This commit is contained in:
commit
fcf8f2111d
25 changed files with 296 additions and 2535 deletions
1
Gemfile
1
Gemfile
|
@ -11,7 +11,6 @@ gem 'puma', '~> 2.11.1'
|
||||||
gem 'exceptional', '~> 2.0.33'
|
gem 'exceptional', '~> 2.0.33'
|
||||||
gem 'oj', '~> 2.5.5'
|
gem 'oj', '~> 2.5.5'
|
||||||
gem 'faraday', '~> 0.9.0'
|
gem 'faraday', '~> 0.9.0'
|
||||||
gem 'gruff', '~> 0.3.6'
|
|
||||||
gem 'nokogiri', '~> 1.6.1'
|
gem 'nokogiri', '~> 1.6.1'
|
||||||
gem 'bbcoder', '~> 1.0.1'
|
gem 'bbcoder', '~> 1.0.1'
|
||||||
gem 'sanitize', '~> 2.1.0'
|
gem 'sanitize', '~> 2.1.0'
|
||||||
|
|
|
@ -119,7 +119,6 @@ GEM
|
||||||
ffi (1.9.3)
|
ffi (1.9.3)
|
||||||
font-awesome-sass (4.1.0)
|
font-awesome-sass (4.1.0)
|
||||||
sass (~> 3.2)
|
sass (~> 3.2)
|
||||||
gruff (0.3.7)
|
|
||||||
haml (4.0.5)
|
haml (4.0.5)
|
||||||
tilt
|
tilt
|
||||||
hike (1.2.3)
|
hike (1.2.3)
|
||||||
|
@ -284,7 +283,6 @@ DEPENDENCIES
|
||||||
factory_girl_rails (~> 4.4.1)
|
factory_girl_rails (~> 4.4.1)
|
||||||
faraday (~> 0.9.0)
|
faraday (~> 0.9.0)
|
||||||
font-awesome-sass (~> 4.1.0.0)
|
font-awesome-sass (~> 4.1.0.0)
|
||||||
gruff (~> 0.3.6)
|
|
||||||
haml (~> 4.0.5)
|
haml (~> 4.0.5)
|
||||||
jquery-rails (~> 2.0.2)
|
jquery-rails (~> 2.0.2)
|
||||||
mysql2 (~> 0.3.15)
|
mysql2 (~> 0.3.15)
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
class ServersController < ApplicationController
|
class ServersController < ApplicationController
|
||||||
before_filter :get_server, except: [:index, :refresh, :new, :create]
|
before_filter :get_server, except: [:index, :refresh, :new, :create]
|
||||||
|
|
||||||
def refresh
|
|
||||||
Server.refresh
|
|
||||||
render :text => t(:servers_updated)
|
|
||||||
end
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@servers = Server.hlds.active.ordered.all :include => :user
|
@servers = Server.hlds.active.ordered.all :include => :user
|
||||||
@ns2 = Server.ns2.active.ordered.all :include => :user
|
@ns2 = Server.ns2.active.ordered.all :include => :user
|
||||||
@hltvs = Server.hltvs.active.ordered.all :include => :user
|
|
||||||
@officials = Server.ns2.active.ordered.where ["name LIKE ?", "%NSL%"]
|
@officials = Server.ns2.active.ordered.where ["name LIKE ?", "%NSL%"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,15 +19,6 @@ class ServersController < ApplicationController
|
||||||
raise AccessError unless @server.can_update? cuser
|
raise AccessError unless @server.can_update? cuser
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin
|
|
||||||
@result = @server.execute params[:query] if params[:query]
|
|
||||||
raise AccessError unless @server.can_update? cuser
|
|
||||||
|
|
||||||
if request.xhr?
|
|
||||||
render partial: 'response', layout: false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@server = Server.new params[:server]
|
@server = Server.new params[:server]
|
||||||
@server.user = cuser
|
@server.user = cuser
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
# title :string(255)
|
# title :string(255)
|
||||||
# status :integer not null
|
# status :integer not null
|
||||||
# category_id :integer
|
# category_id :integer
|
||||||
# text :text
|
# text :text(16777215)
|
||||||
# user_id :integer
|
# user_id :integer
|
||||||
# created_at :datetime
|
# created_at :datetime
|
||||||
# updated_at :datetime
|
# updated_at :datetime
|
||||||
# version :integer
|
# version :integer
|
||||||
# text_parsed :text
|
# text_parsed :text(16777215)
|
||||||
# text_coding :integer default(0), not null
|
# text_coding :integer default(0), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
# created_at :datetime
|
# created_at :datetime
|
||||||
# updated_at :datetime
|
# updated_at :datetime
|
||||||
# votes :integer default(0), not null
|
# votes :integer default(0), not null
|
||||||
|
# status :integer default(0), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class Gatherer < ActiveRecord::Base
|
class Gatherer < ActiveRecord::Base
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
# points1 :integer
|
# points1 :integer
|
||||||
# points2 :integer
|
# points2 :integer
|
||||||
# hltv_id :integer
|
# hltv_id :integer
|
||||||
|
# caster_id :string(255)
|
||||||
#
|
#
|
||||||
|
|
||||||
class Match < ActiveRecord::Base
|
class Match < ActiveRecord::Base
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
# steam_profile :string(255)
|
# steam_profile :string(255)
|
||||||
# achievements_parsed :string(255)
|
# achievements_parsed :string(255)
|
||||||
# signature_parsed :string(255)
|
# signature_parsed :string(255)
|
||||||
|
# stream :string(255)
|
||||||
|
# layout :string(255)
|
||||||
#
|
#
|
||||||
|
|
||||||
class Profile < ActiveRecord::Base
|
class Profile < ActiveRecord::Base
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
# category_id :integer
|
# category_id :integer
|
||||||
#
|
#
|
||||||
|
|
||||||
require "rcon"
|
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
class Server < ActiveRecord::Base
|
class Server < ActiveRecord::Base
|
||||||
|
@ -40,20 +39,16 @@ class Server < ActiveRecord::Base
|
||||||
DOMAIN_HLDS = 0
|
DOMAIN_HLDS = 0
|
||||||
DOMAIN_HLTV = 1
|
DOMAIN_HLTV = 1
|
||||||
DOMAIN_NS2 = 2
|
DOMAIN_NS2 = 2
|
||||||
HLTV_IDLE = 1200
|
|
||||||
DEMOS = "/var/www/virtual/ensl.org/hlds_l/ns/demos"
|
|
||||||
QSTAT = "/usr/bin/quakestat"
|
|
||||||
TMPFILE = "tmp/server.txt"
|
|
||||||
|
|
||||||
attr_accessor :rcon_handle, :pwd
|
attr_accessor :pwd
|
||||||
attr_protected :id, :user_id, :updated_at, :created_at, :map, :players, :maxplayers, :ping, :version
|
attr_protected :id, :user_id, :updated_at, :created_at, :map, :players, :maxplayers, :ping, :version
|
||||||
|
|
||||||
validates_length_of [:name, :dns,], :in => 1..30
|
validates_length_of [:name, :dns,], :in => 1..30
|
||||||
validates_length_of [:rcon, :password, :irc], :maximum => 30, :allow_blank => true
|
validates_length_of [:rcon, :password, :irc], :maximum => 30, :allow_blank => true
|
||||||
validates_length_of :description, :maximum => 255, :allow_blank => true
|
validates_length_of :description, :maximum => 255, :allow_blank => true
|
||||||
validates_format_of :ip, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\z/
|
validates_format_of :ip, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\z/
|
||||||
validates_format_of :port, :with => /\A[0-9]{1,5}\z/
|
validates_format_of :port, :with => /\A[0-9]{1,5}\z/
|
||||||
validates_format_of :reservation, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}\z/, :allow_nil => true
|
validates_format_of :reservation, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}\z/, :allow_nil => true
|
||||||
validates_format_of :pwd, :with => /\A[A-Za-z0-9_\-]*\z/, :allow_nil => true
|
validates_format_of :pwd, :with => /\A[A-Za-z0-9_\-]*\z/, :allow_nil => true
|
||||||
|
|
||||||
scope :ordered, :order => "name"
|
scope :ordered, :order => "name"
|
||||||
|
@ -71,301 +66,79 @@ class Server < ActiveRecord::Base
|
||||||
AND match_time > '#{(time.ago(Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'
|
AND match_time > '#{(time.ago(Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'
|
||||||
AND match_time < '#{(time.ago(-Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'",
|
AND match_time < '#{(time.ago(-Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'",
|
||||||
:conditions => "matches.hltv_id IS NULL"} }
|
:conditions => "matches.hltv_id IS NULL"} }
|
||||||
scope :of_addr,
|
|
||||||
lambda { |addr| {
|
|
||||||
:conditions => {
|
|
||||||
:ip => addr.match(/([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/)[0],
|
|
||||||
:port => addr.match(/:([0-9]{1,5})/)[1] } } }
|
|
||||||
scope :of_category,
|
|
||||||
lambda { |category| {
|
|
||||||
:conditions => {:category_id => category.id} }}
|
|
||||||
|
|
||||||
has_many :logs
|
has_many :logs
|
||||||
has_many :matches
|
has_many :matches
|
||||||
has_many :challenges
|
has_many :challenges
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :recordable, :polymorphic => true
|
belongs_to :recordable, :polymorphic => true
|
||||||
|
|
||||||
before_create :set_category
|
before_create :set_category
|
||||||
|
|
||||||
acts_as_versioned
|
acts_as_versioned
|
||||||
non_versioned_columns << 'name'
|
non_versioned_columns << 'name'
|
||||||
non_versioned_columns << 'description'
|
non_versioned_columns << 'description'
|
||||||
non_versioned_columns << 'dns'
|
non_versioned_columns << 'dns'
|
||||||
non_versioned_columns << 'ip'
|
non_versioned_columns << 'ip'
|
||||||
non_versioned_columns << 'port'
|
non_versioned_columns << 'port'
|
||||||
non_versioned_columns << 'rcon'
|
non_versioned_columns << 'rcon'
|
||||||
non_versioned_columns << 'password'
|
non_versioned_columns << 'password'
|
||||||
non_versioned_columns << 'irc'
|
non_versioned_columns << 'irc'
|
||||||
non_versioned_columns << 'user_id'
|
non_versioned_columns << 'user_id'
|
||||||
non_versioned_columns << 'official'
|
non_versioned_columns << 'official'
|
||||||
non_versioned_columns << 'domain'
|
non_versioned_columns << 'domain'
|
||||||
non_versioned_columns << 'reservation'
|
non_versioned_columns << 'reservation'
|
||||||
non_versioned_columns << 'recording'
|
non_versioned_columns << 'recording'
|
||||||
non_versioned_columns << 'idle'
|
non_versioned_columns << 'idle'
|
||||||
non_versioned_columns << 'default_id'
|
non_versioned_columns << 'default_id'
|
||||||
non_versioned_columns << 'active'
|
non_versioned_columns << 'active'
|
||||||
non_versioned_columns << 'recordable_type'
|
non_versioned_columns << 'recordable_type'
|
||||||
non_versioned_columns << 'recordable_id'
|
non_versioned_columns << 'recordable_id'
|
||||||
|
|
||||||
def domains
|
def domains
|
||||||
{DOMAIN_HLTV => "HLTV", DOMAIN_HLDS => "NS Server", DOMAIN_NS2 => "NS2 Server"}
|
{DOMAIN_HLTV => "HLTV", DOMAIN_HLDS => "NS Server", DOMAIN_NS2 => "NS2 Server"}
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
name
|
|
||||||
end
|
|
||||||
|
|
||||||
def addr
|
|
||||||
ip + ":" + port.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def players_s
|
|
||||||
if players.nil? or max_players.nil?
|
|
||||||
"N/A"
|
|
||||||
else
|
|
||||||
players.to_s + " / " + max_players.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def recording_s
|
|
||||||
return nil if self.domain != DOMAIN_HLTV
|
|
||||||
# recording.to_i > 0 ? Match.find(recording).to_s : recording
|
|
||||||
recording
|
|
||||||
end
|
|
||||||
|
|
||||||
def reservation_s
|
|
||||||
return nil if domain != DOMAIN_HLTV
|
|
||||||
reservation
|
|
||||||
end
|
|
||||||
|
|
||||||
def graphfile
|
|
||||||
File.join("public", "images", "servers", id.to_s + ".png")
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_category
|
|
||||||
self.category_id = (domain == DOMAIN_NS2 ? 45 : 44 )
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_validation_on_update
|
|
||||||
if reservation_changed?
|
|
||||||
rcon_connect
|
|
||||||
if reservation == nil
|
|
||||||
rcon_exec "stop"
|
|
||||||
self.recording = nil
|
|
||||||
self.recordable = nil
|
|
||||||
self.idle = nil
|
|
||||||
save_demos if self.recording
|
|
||||||
hltv_stop
|
|
||||||
else
|
|
||||||
if changes['reservation'][0].nil?
|
|
||||||
hltv_start
|
|
||||||
rcon_exec "stop"
|
|
||||||
self.recording = recordable.demo_name if recordable and recordable_type == "Match" or recordable_type == "Gather"
|
|
||||||
rcon_exec "record demos/" + self.recording if self.recording
|
|
||||||
end
|
|
||||||
rcon_exec "serverpassword " + pwd
|
|
||||||
rcon_exec "connect " + reservation
|
|
||||||
self.idle = DateTime.now
|
|
||||||
end
|
|
||||||
rcon_disconnect
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute command
|
|
||||||
rcon_connect
|
|
||||||
response = rcon_exec command
|
|
||||||
rcon_disconnect
|
|
||||||
response
|
|
||||||
end
|
|
||||||
|
|
||||||
def rcon_connect
|
|
||||||
self.rcon_handle = RCon::Query::Original.new(ip, port, rcon)
|
|
||||||
end
|
|
||||||
|
|
||||||
def rcon_exec command
|
|
||||||
response = rcon_handle.command(command)
|
|
||||||
|
|
||||||
Log.transaction do
|
|
||||||
Log.add(self, Log::DOMAIN_RCON_COMMAND, command)
|
|
||||||
if response.to_s.length > 0
|
|
||||||
Log.add(self, Log::DOMAIN_RCON_RESPONSE, response)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
response
|
|
||||||
end
|
|
||||||
|
|
||||||
def rcon_disconnect
|
|
||||||
rcon_handle.disconnect
|
|
||||||
end
|
|
||||||
|
|
||||||
def hltv_start
|
|
||||||
if nr = hltv_nr
|
|
||||||
`screen -d -m -S "Hltv-#{nr[1]}" -c $HOME/.screenrc-hltv $HOME/hlds_l/hltv -ip 78.46.36.107 -port 28#{nr[1]}00 +exec ns/hltv#{nr[1]}.cfg`
|
|
||||||
sleep 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hltv_nr
|
|
||||||
self.name.match(/Tv \#([0-9])/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def hltv_stop
|
|
||||||
if nr = hltv_nr
|
|
||||||
sleep 5
|
|
||||||
rcon_exec "exit"
|
|
||||||
#`screen -S "Hltv-#{nr[1]}" -X 'quit'`
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def save_demos
|
|
||||||
dir = case recordable_type
|
|
||||||
when "Match" then
|
|
||||||
recordable.contest.demos
|
|
||||||
when "Gather" then
|
|
||||||
Directory.find(Directory::DEMOS_GATHERS)
|
|
||||||
end
|
|
||||||
|
|
||||||
dir ||= Directory.find(Directory::DEMOS_DEFAULT)
|
|
||||||
zip_path = File.join(dir.path, recording + ".zip")
|
|
||||||
|
|
||||||
Zip::ZipOutputStream::open(zip_path) do |zos|
|
|
||||||
if recordable_type == "Match"
|
|
||||||
zos.put_next_entry "readme.txt"
|
|
||||||
zos.write "Team1: " + recordable.contester1.to_s + "\r\n"
|
|
||||||
zos.write "Team2: " + recordable.contester2.to_s + "\r\n"
|
|
||||||
zos.write "Date: " + recordable.match_time.to_s + "\r\n"
|
|
||||||
zos.write "Contest: " + recordable.contest.to_s + "\r\n"
|
|
||||||
zos.write "Server: " + recordable.server.addr + "\r\n" if recordable.server
|
|
||||||
zos.write "HLTV: " + addr + "\r\n"
|
|
||||||
zos.write YAML::dump(recordable.attributes).to_s
|
|
||||||
end
|
|
||||||
Dir.glob("#{DEMOS}/*").each do |file|
|
|
||||||
if File.file?(file) and file.match(/#{recording}.*\.dem/)
|
|
||||||
zos.put_next_entry File.basename(file)
|
|
||||||
zos.write(IO.read(file))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DataFile.transaction do
|
|
||||||
unless dbfile = DataFile.find_by_path(zip_path)
|
|
||||||
dbfile = DataFile.new
|
|
||||||
dbfile.path = zip_path
|
|
||||||
dbfile.directory = dir
|
|
||||||
dbfile.save!
|
|
||||||
DataFile.update_all({:name => File.basename(zip_path)}, {:id => dbfile.id})
|
|
||||||
end
|
|
||||||
if recordable_type == "Match"
|
|
||||||
recordable.demo = dbfile
|
|
||||||
recordable.save
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_stats
|
|
||||||
graph = Gruff::Line.new
|
|
||||||
graph.title = name
|
|
||||||
pings = []
|
|
||||||
players = []
|
|
||||||
labels = {}
|
|
||||||
n = 0
|
|
||||||
|
|
||||||
for version in versions.all(:order => "updated_at DESC", :limit => 30).reverse
|
|
||||||
pings << version.ping.to_i
|
|
||||||
players << version.players.to_i
|
|
||||||
labels[n] = version.updated_at.strftime("%H:%M") if n % 3 == 0
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
graph.theme_37signals
|
|
||||||
graph.data("Ping", pings, '#057fc0')
|
|
||||||
graph.data("Players", players, '#ff0000')
|
|
||||||
graph.labels = labels
|
|
||||||
graph.write(graphfile)
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_record
|
|
||||||
# if self.default
|
|
||||||
# rcon_exec "record demos/auto-" + Verification.uncrap(default.name)
|
|
||||||
# rcon_exec "serverpassword " + default.password
|
|
||||||
# rcon_exec "connect " + default.addr
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_free time
|
|
||||||
challenges.around(time).pending.count == 0 and matches.around(time).count == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_create? cuser
|
|
||||||
cuser
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_update? cuser
|
|
||||||
cuser and cuser.admin? or user == cuser
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_destroy? cuser
|
|
||||||
cuser and cuser.admin?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.refresh
|
|
||||||
servers = ""
|
|
||||||
Server.hlds.active.all.each do |server|
|
|
||||||
servers << " -a2s " + server.ip + ":" + server.port.to_s
|
|
||||||
end
|
end
|
||||||
|
|
||||||
file = File.join(Rails.root, TMPFILE)
|
def to_s
|
||||||
system "#{QSTAT} -xml #{servers} | grep -v '<name>' > #{file}"
|
name
|
||||||
|
end
|
||||||
|
|
||||||
doc = REXML::Document.new(File.new(file).read)
|
def addr
|
||||||
doc.elements.each('qstat/server') do |server|
|
ip + ":" + port.to_s
|
||||||
hostname = server.elements['hostname'].text.split(':', 2)
|
end
|
||||||
if s = Server.active.first(:conditions => {:ip => hostname[0], :port => hostname[1]})
|
|
||||||
if server.elements.include? 'map'
|
def set_category
|
||||||
s.map = server.elements['map'].text
|
self.category_id = (domain == DOMAIN_NS2 ? 45 : 44 )
|
||||||
s.players = server.elements['numplayers'].text.to_i
|
end
|
||||||
s.max_players = server.elements['maxplayers'].text.to_i
|
|
||||||
s.ping = server.elements['ping'].text
|
def is_free time
|
||||||
s.map = server.elements['map'].text
|
challenges.around(time).pending.count == 0 and matches.around(time).count == 0
|
||||||
s.save
|
end
|
||||||
s.make_stats
|
|
||||||
end
|
def can_create? cuser
|
||||||
|
cuser
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_update? cuser
|
||||||
|
cuser and cuser.admin? or user == cuser
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_destroy? cuser
|
||||||
|
cuser and cuser.admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.move addr, newaddr, newpwd
|
||||||
|
self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
|
||||||
|
hltv.reservation = newaddr
|
||||||
|
hltv.pwd = newpwd
|
||||||
|
hltv.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
servers = ""
|
def self.stop addr
|
||||||
Server.hltvs.reserved.each do |server|
|
self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
|
||||||
servers << " -a2s #{server.reservation}"
|
hltv.reservation = nil
|
||||||
end
|
hltv.save!
|
||||||
|
|
||||||
doc = REXML::Document.new(`#{QSTAT} -xml #{servers} | grep -v '<name>'`)
|
|
||||||
doc.elements.each('qstat/server') do |server|
|
|
||||||
hostname = server.elements['hostname'].text.split(':', 2)
|
|
||||||
if s = Server.hltvs.reserved.first(:conditions => {:ip => hostname[0], :port => hostname[1]})
|
|
||||||
if server.elements['numplayers'].text.to_i > 0
|
|
||||||
s.update_attribute :idle, DateTime.now
|
|
||||||
elsif (s.idle + HLTV_IDLE).past?
|
|
||||||
s.reservation = nil
|
|
||||||
s.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.move addr, newaddr, newpwd
|
|
||||||
self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
|
|
||||||
hltv.reservation = newaddr
|
|
||||||
hltv.pwd = newpwd
|
|
||||||
hltv.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.stop addr
|
|
||||||
self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
|
|
||||||
hltv.reservation = nil
|
|
||||||
hltv.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<% @server.logs.recent.reverse_each do |log| %>
|
|
||||||
<% if log.domain == Log::DOMAIN_RCON_COMMAND %>
|
|
||||||
<pre class="command"><%= log.text %></pre>
|
|
||||||
<% elsif log.domain == Log::DOMAIN_RCON_RESPONSE %>
|
|
||||||
<pre class="response"><%= log.text %></pre>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<% @server.logs.recent.reverse_each do |log| %>
|
|
||||||
<% if log.domain == Log::DOMAIN_RCON_COMMAND %>
|
|
||||||
<pre class="command"><%= log.text %></pre>
|
|
||||||
<% elsif log.domain == Log::DOMAIN_RCON_RESPONSE %>
|
|
||||||
<pre class="response"><%= log.text %></pre>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<h1>
|
|
||||||
RCON: <%= h @server %>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<%= image_tag "icons/spinner.gif",
|
|
||||||
:align => "absmiddle",
|
|
||||||
:id => "spinner",
|
|
||||||
:style =>"display: none;" %>
|
|
||||||
|
|
||||||
<div class="wide box" id="serverLog">
|
|
||||||
<%= render :partial => "response" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% form_tag remote: true, update: 'serverLog',
|
|
||||||
:before => "Element.show('spinner')",
|
|
||||||
:success => "Element.hide('spinner'); $('serverLog').scrollTop = $('serverLog').scrollHeight;" do %>
|
|
||||||
<%= label_tag :query, "Rcon Command:" %>
|
|
||||||
<%= text_field_tag "query", params['query'], :size => 30 %>
|
|
||||||
<% end %>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<h1>
|
|
||||||
Server Log: <%= h @server %>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="box wide">
|
|
||||||
<table class="data">
|
|
||||||
<tr>
|
|
||||||
<th width="10%">Date</th>
|
|
||||||
<th width="10%">Type</th>
|
|
||||||
<th width="80%">Message</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<% @server.logs.each do |log| %>
|
|
||||||
<tr class="<%= cycle('even', 'odd') %>">
|
|
||||||
<td><%= shorttime log.created_at %></td>
|
|
||||||
<td><%= log.domains[log.domain] %></td>
|
|
||||||
<td><%= h log.text %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
12
spec/factories/group.rb
Normal file
12
spec/factories/group.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :group do
|
||||||
|
sequence(:id) { |n| n + 100 } # Preserve first 100
|
||||||
|
sequence(:name) { |n| "Group#{n}" }
|
||||||
|
association :founder, factory: :user
|
||||||
|
end
|
||||||
|
|
||||||
|
trait :admin do
|
||||||
|
name "Admins"
|
||||||
|
id Group::ADMINS
|
||||||
|
end
|
||||||
|
end
|
5
spec/factories/grouper.rb
Normal file
5
spec/factories/grouper.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :grouper do
|
||||||
|
sequence(:task) { |n| "Task#{n}" }
|
||||||
|
end
|
||||||
|
end
|
8
spec/factories/server.rb
Normal file
8
spec/factories/server.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :server do
|
||||||
|
sequence(:name) { |n| "ServerName#{n}" }
|
||||||
|
sequence(:dns) { |n| "DNS#{n}" }
|
||||||
|
sequence(:ip) { |n| "192.168.#{n % 255}.#{n}" }
|
||||||
|
sequence(:port) { |n| "#{1000 + n}" }
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,6 +13,13 @@ FactoryGirl.define do
|
||||||
create(:profile, user: user)
|
create(:profile, user: user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :admin do
|
||||||
|
after(:create) do |user|
|
||||||
|
group = create(:group, :admin)
|
||||||
|
create :grouper, user: user, group: group
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
factory :user_with_team do
|
factory :user_with_team do
|
||||||
after(:create) do |user|
|
after(:create) do |user|
|
||||||
create(:team, founder: user)
|
create(:team, founder: user)
|
||||||
|
|
30
spec/features/servers/server_administration.rb
Normal file
30
spec/features/servers/server_administration.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Server Administration' do
|
||||||
|
let!(:admin) { create :user, :admin }
|
||||||
|
|
||||||
|
background do
|
||||||
|
sign_in_as admin
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'creating a server' do
|
||||||
|
visit servers_path
|
||||||
|
expect(page).to have_content('Listing Servers')
|
||||||
|
click_link 'New server'
|
||||||
|
test_server_creation_and_editing
|
||||||
|
visit servers_path
|
||||||
|
expect(page).to have_content Server.last.name
|
||||||
|
end
|
||||||
|
|
||||||
|
feature 'Server deletion' do
|
||||||
|
let!(:server) { create :server }
|
||||||
|
scenario 'deleting a server' do
|
||||||
|
visit servers_path
|
||||||
|
expect(page).to have_content(server.name)
|
||||||
|
visit server_path(server)
|
||||||
|
click_link 'Delete Server'
|
||||||
|
visit servers_path
|
||||||
|
expect(page).to_not have_content(server.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
14
spec/features/servers/user_servers.rb
Normal file
14
spec/features/servers/user_servers.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'User created servers' do
|
||||||
|
let!(:user) { create :user }
|
||||||
|
|
||||||
|
background do
|
||||||
|
sign_in_as user
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Creating and updating a server' do
|
||||||
|
visit new_server_path
|
||||||
|
test_server_creation_and_editing
|
||||||
|
end
|
||||||
|
end
|
98
spec/models/server_spec.rb
Normal file
98
spec/models/server_spec.rb
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: servers
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# name :string(255)
|
||||||
|
# description :string(255)
|
||||||
|
# dns :string(255)
|
||||||
|
# ip :string(255)
|
||||||
|
# port :string(255)
|
||||||
|
# rcon :string(255)
|
||||||
|
# password :string(255)
|
||||||
|
# irc :string(255)
|
||||||
|
# user_id :integer
|
||||||
|
# official :boolean
|
||||||
|
# created_at :datetime
|
||||||
|
# updated_at :datetime
|
||||||
|
# map :string(255)
|
||||||
|
# players :integer
|
||||||
|
# max_players :integer
|
||||||
|
# ping :string(255)
|
||||||
|
# version :integer
|
||||||
|
# domain :integer default(0), not null
|
||||||
|
# reservation :string(255)
|
||||||
|
# recording :string(255)
|
||||||
|
# idle :datetime
|
||||||
|
# default_id :integer
|
||||||
|
# active :boolean default(TRUE), not null
|
||||||
|
# recordable_type :string(255)
|
||||||
|
# recordable_id :integer
|
||||||
|
# category_id :integer
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Server do
|
||||||
|
describe 'create' do
|
||||||
|
it 'sets category to 45 if domain is NS2' do
|
||||||
|
server = create :server, domain: Server::DOMAIN_NS2
|
||||||
|
expect(server.category_id).to eq(45)
|
||||||
|
end
|
||||||
|
it 'sets category to 44 if domain is not NS2' do
|
||||||
|
server = create :server, domain: Server::DOMAIN_HLDS
|
||||||
|
expect(server.category_id).to eq(44)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'addr' do
|
||||||
|
it 'returns properly formatted IP and port number' do
|
||||||
|
ip = '1.1.1.1'
|
||||||
|
port = '8000'
|
||||||
|
server = create :server, ip: ip, port: port
|
||||||
|
expect(server.addr).to eq('1.1.1.1:8000')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'to_s' do
|
||||||
|
it 'returns server name' do
|
||||||
|
server_name = "Foo"
|
||||||
|
server = create :server, name: server_name
|
||||||
|
expect(server.to_s).to eq(server_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'Permissions' do
|
||||||
|
let!(:user) { create :user }
|
||||||
|
let!(:admin) { create :user, :admin }
|
||||||
|
let!(:server_user) {create :user }
|
||||||
|
let!(:server) { create :server, user: server_user }
|
||||||
|
|
||||||
|
describe 'can_create?' do
|
||||||
|
it 'returns true for non-admins' do
|
||||||
|
expect(server.can_create? user).to be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'can_destroy?' do
|
||||||
|
it 'returns true for admin' do
|
||||||
|
expect(server.can_destroy? admin).to be_true
|
||||||
|
end
|
||||||
|
it 'returns false for non-admins' do
|
||||||
|
expect(server.can_destroy? user).to be_false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'can_update?' do
|
||||||
|
it 'returns true for admin' do
|
||||||
|
expect(server.can_update? admin).to be_true
|
||||||
|
end
|
||||||
|
it 'returns true if server belongs to user' do
|
||||||
|
expect(server.can_update? server_user).to be_true
|
||||||
|
end
|
||||||
|
it 'returns false for non-admins' do
|
||||||
|
expect(server.can_update? user).to be_false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,6 +26,7 @@ RSpec.configure do |config|
|
||||||
config.include FactoryGirl::Syntax::Methods
|
config.include FactoryGirl::Syntax::Methods
|
||||||
config.include Controllers::JsonHelpers, type: :controller
|
config.include Controllers::JsonHelpers, type: :controller
|
||||||
config.include Features::FormHelpers, type: :feature
|
config.include Features::FormHelpers, type: :feature
|
||||||
|
config.include Features::ServerHelpers, type: :feature
|
||||||
config.include Features::SessionHelpers, type: :feature
|
config.include Features::SessionHelpers, type: :feature
|
||||||
|
|
||||||
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
||||||
|
|
49
spec/support/features/server_helpers.rb
Normal file
49
spec/support/features/server_helpers.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
module Features
|
||||||
|
module ServerHelpers
|
||||||
|
def test_server_creation_and_editing
|
||||||
|
dns = 'ServerDns.com'
|
||||||
|
ip = '192.168.1.1'
|
||||||
|
port = '8000'
|
||||||
|
rcon = 'whatsrcon'
|
||||||
|
password = 'secret'
|
||||||
|
name = 'MyNsServer'
|
||||||
|
description = 'My NS Server'
|
||||||
|
irc = '#some_channel'
|
||||||
|
|
||||||
|
visit new_server_path
|
||||||
|
fill_in 'Dns', with: dns
|
||||||
|
fill_in 'server_ip', with: ip
|
||||||
|
fill_in 'server_port', with: port
|
||||||
|
fill_in 'Password', with: password
|
||||||
|
fill_in 'Name', with: name
|
||||||
|
fill_in 'Description', with: description
|
||||||
|
fill_in 'Irc', with: irc
|
||||||
|
check 'Available for officials?'
|
||||||
|
click_button 'Save'
|
||||||
|
|
||||||
|
expect(page).to have_content(dns)
|
||||||
|
expect(page).to have_content("#{ip}:#{port}")
|
||||||
|
expect(page).to have_content(password)
|
||||||
|
expect(page).to have_content(irc)
|
||||||
|
expect(page).to have_content(description)
|
||||||
|
|
||||||
|
click_link 'Edit Server'
|
||||||
|
|
||||||
|
fill_in 'Dns', with: "#{dns}2"
|
||||||
|
fill_in 'server_ip', with: "192.168.1.2"
|
||||||
|
fill_in 'server_port', with: "8001"
|
||||||
|
fill_in 'Password', with: "#{password}2"
|
||||||
|
fill_in 'Name', with: "#{name}2"
|
||||||
|
fill_in 'Description', with: "#{description}2"
|
||||||
|
fill_in 'Irc', with: "#{irc}2"
|
||||||
|
check 'Available for officials?'
|
||||||
|
click_button 'Save'
|
||||||
|
|
||||||
|
expect(page).to have_content("192.168.1.2:8001")
|
||||||
|
expect(page).to have_content("#{dns}2")
|
||||||
|
expect(page).to have_content("#{password}2")
|
||||||
|
expect(page).to have_content("#{irc}2")
|
||||||
|
expect(page).to have_content("#{description}2")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
296
vendor/plugins/rcon/bin/rcontool
vendored
296
vendor/plugins/rcon/bin/rcontool
vendored
|
@ -1,296 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# rcontool - shell interface to rcon commands
|
|
||||||
#
|
|
||||||
# (C) 2006 Erik Hollensbe, License details below
|
|
||||||
#
|
|
||||||
# Use 'rcontool -h' for usage instructions.
|
|
||||||
#
|
|
||||||
# The compilation of software known as rcontool is distributed under the
|
|
||||||
# following terms:
|
|
||||||
# Copyright (C) 2005-2006 Erik Hollensbe. All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source form, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
||||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
||||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
# SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
#
|
|
||||||
# rubygems hack
|
|
||||||
#
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'rubygems'
|
|
||||||
rescue LoadError => e
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
require 'rcon'
|
|
||||||
require 'ip'
|
|
||||||
rescue LoadError => e
|
|
||||||
$stderr.puts "rcontool requires the rcon and ip libraries be installed."
|
|
||||||
$stderr.puts "You can find them both via rubygems or at http://rubyforge.org."
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
RCONTOOL_VERSION = '0.1.0'
|
|
||||||
|
|
||||||
require 'optparse'
|
|
||||||
require 'ostruct'
|
|
||||||
|
|
||||||
#
|
|
||||||
# Manages our options
|
|
||||||
#
|
|
||||||
|
|
||||||
def get_options
|
|
||||||
options = OpenStruct.new
|
|
||||||
# ip address (IP::Address object)
|
|
||||||
options.ip_address = nil
|
|
||||||
# port (integer)
|
|
||||||
options.port = nil
|
|
||||||
# password
|
|
||||||
options.password = nil
|
|
||||||
# protocol type (one of :hlds, :source, :oldquake, :newquake)
|
|
||||||
options.protocol_type = nil
|
|
||||||
# verbose, spit out extra information
|
|
||||||
options.verbose = false
|
|
||||||
# command to execute on the server
|
|
||||||
options.command = nil
|
|
||||||
|
|
||||||
optparse = OptionParser.new do |opts|
|
|
||||||
opts.banner = "Usage: #{File.basename $0} <ip_address:port> <command> [options]"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Options:"
|
|
||||||
|
|
||||||
opts.on("--ip-address [ADDRESS]",
|
|
||||||
"Provide an IP address to connect to. Does not take a port.") do |ip_address|
|
|
||||||
if ! options.ip_address.nil?
|
|
||||||
$stderr.puts "Error: you have already provided an IP Address."
|
|
||||||
$stderr.puts opts
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
options.ip_address = IP::Address.new(ip_address)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-r", "--port [PORT]",
|
|
||||||
"Port to connect to.") do |port|
|
|
||||||
if ! options.port.nil?
|
|
||||||
$stderr.puts "Error: you have already provided a port."
|
|
||||||
$stderr.puts opts
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
options.port = port.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-c", "--command [COMMAND]",
|
|
||||||
"Command to run on the server.") do |command|
|
|
||||||
if ! options.command.nil?
|
|
||||||
$stderr.puts "Error: you have already provided a command."
|
|
||||||
$stderr.puts opts
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
options.command = command
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-p", "--password [PASSWORD]",
|
|
||||||
"Provide a password on the command line.") do |password|
|
|
||||||
options.password = password
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-f", "--password-from [FILENAME]",
|
|
||||||
"Get the password from a file (use '/dev/fd/0' or '/dev/stdin' to read from Standard Input).") do |filename|
|
|
||||||
if !filename.nil?
|
|
||||||
f = File.open(filename)
|
|
||||||
options.password = f.gets.chomp
|
|
||||||
f.close
|
|
||||||
else
|
|
||||||
$stderr.puts "Error: filename (from -f) is not valid."
|
|
||||||
$stderr.puts opts
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-t", "--protocol-type [TYPE]", [:hlds, :source, :oldquake, :newquake],
|
|
||||||
"Type of rcon connection to make: (hlds, source, oldquake, newquake).",
|
|
||||||
" Note: oldquake is quake1/quakeworld, newquake is quake2/3.") do |protocol_type|
|
|
||||||
options.protocol_type = protocol_type
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-v", "--[no-]verbose",
|
|
||||||
"Run verbosely, print information about each packet recieved and turnaround times.") do |verbose|
|
|
||||||
options.verbose = verbose
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("-h", "--help",
|
|
||||||
"This help message.") do
|
|
||||||
$stderr.puts opts
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on("--version", "Print the version information.") do
|
|
||||||
$stderr.puts "This is rcontool version #{RCONTOOL_VERSION},"
|
|
||||||
$stderr.puts "it is located at #{File.expand_path $0}."
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Note: IP, port, protocol type, password and command are required to function."
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Examples (all are equivalent):"
|
|
||||||
opts.separator "\t#{File.basename($0)} 10.0.0.11 status -t hlds -r 27015 -p foobar"
|
|
||||||
opts.separator "\techo 'foobar' | #{File.basename($0)} 10.0.0.11:27015 status -t hlds -f /dev/stdin"
|
|
||||||
opts.separator "\t#{File.basename($0)} --ip-address 10.0.0.11 --port 27015 -c status -t hlds -f file_with_password"
|
|
||||||
opts.separator ""
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# This hackery is to help facilitate the bareword options if
|
|
||||||
# they exist, while still allowing for the option parser
|
|
||||||
# to work properly.
|
|
||||||
#
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
s1 = ARGV.shift
|
|
||||||
s2 = ARGV.shift
|
|
||||||
|
|
||||||
begin
|
|
||||||
options.ip_address = IP::Address::IPv4.new(s1)
|
|
||||||
options.command = s2
|
|
||||||
rescue IP::AddressException => e
|
|
||||||
# attempt to split it first... not sure how to best handle this situation
|
|
||||||
begin
|
|
||||||
ip,port = s1.split(/:/, 2)
|
|
||||||
options.ip_address = IP::Address::IPv4.new(ip)
|
|
||||||
options.port = port.to_i
|
|
||||||
options.command = s2
|
|
||||||
rescue Exception => e
|
|
||||||
end
|
|
||||||
|
|
||||||
if [options.ip_address, options.port].include? nil
|
|
||||||
ARGV.unshift(s2)
|
|
||||||
ARGV.unshift(s1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
optparse.parse!
|
|
||||||
|
|
||||||
if [options.ip_address, options.protocol_type, options.port, options.password, options.command].include? nil
|
|
||||||
$stderr.puts optparse
|
|
||||||
exit -1
|
|
||||||
end
|
|
||||||
|
|
||||||
return options
|
|
||||||
end
|
|
||||||
|
|
||||||
def verbose(string)
|
|
||||||
$stderr.puts string if $options.verbose
|
|
||||||
end
|
|
||||||
|
|
||||||
def dump_source_packet(packet)
|
|
||||||
if $options.verbose
|
|
||||||
verbose "Request ID: #{packet.request_id}"
|
|
||||||
verbose "Packet Size: #{packet.packet_size}"
|
|
||||||
verbose "Response Type: #{packet.command_type}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# start main block
|
|
||||||
#
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
$options = get_options
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# Source query
|
|
||||||
#
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
if $options.protocol_type == :source
|
|
||||||
verbose "Protocol type 'SOURCE' selected."
|
|
||||||
|
|
||||||
rcon = RCon::Query::Source.new($options.ip_address.ip_address, $options.port)
|
|
||||||
|
|
||||||
# if we have a verbose request, give all the information we can about
|
|
||||||
# the query, including the packet information.
|
|
||||||
rcon.return_packets = $options.verbose
|
|
||||||
|
|
||||||
verbose "Attempting authentication to #{$options.ip_address.ip_address}:#{$options.port} with password '#{$options.password}'"
|
|
||||||
|
|
||||||
value = rcon.auth $options.password
|
|
||||||
|
|
||||||
dump_source_packet value
|
|
||||||
|
|
||||||
if ($options.verbose && value.command_type == RCon::Packet::Source::RESPONSE_AUTH) || value
|
|
||||||
verbose "Authentication succeeded. Sending command: '#{$options.command}'"
|
|
||||||
|
|
||||||
value = rcon.command $options.command
|
|
||||||
|
|
||||||
dump_source_packet value
|
|
||||||
verbose ""
|
|
||||||
|
|
||||||
if $options.verbose
|
|
||||||
puts value.string1
|
|
||||||
else
|
|
||||||
puts value
|
|
||||||
end
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
$stderr.puts "Authentication failed."
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# Original Query
|
|
||||||
#
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
else
|
|
||||||
rcon = nil
|
|
||||||
case $options.protocol_type
|
|
||||||
when :hlds
|
|
||||||
verbose "Protocol type 'HLDS' selected"
|
|
||||||
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
|
||||||
RCon::Query::Original::HLDS)
|
|
||||||
when :oldquake
|
|
||||||
verbose "Protocol type 'OLDQUAKE' selected"
|
|
||||||
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
|
||||||
RCon::Query::Original::QUAKEWORLD)
|
|
||||||
when :newquake
|
|
||||||
verbose "Protocol type 'NEWQUAKE' selected"
|
|
||||||
rcon = RCon::Query::Original.new($options.ip_address.ip_address, $options.port, $options.password,
|
|
||||||
RCon::Query::Original::NEWQUAKE)
|
|
||||||
end
|
|
||||||
verbose "Attempting transmission to #{$options.ip_address.ip_address}:#{$options.port}"
|
|
||||||
verbose "Using password: '#{$options.password}' and sending command: '#{$options.command}'"
|
|
||||||
verbose ""
|
|
||||||
string = rcon.command($options.command)
|
|
||||||
|
|
||||||
puts string
|
|
||||||
exit 0
|
|
||||||
end
|
|
||||||
|
|
499
vendor/plugins/rcon/lib/rcon.rb
vendored
499
vendor/plugins/rcon/lib/rcon.rb
vendored
|
@ -1,499 +0,0 @@
|
||||||
# encoding: US-ASCII
|
|
||||||
|
|
||||||
require 'socket'
|
|
||||||
|
|
||||||
#
|
|
||||||
# RCon is a module to work with Quake 1/2/3, Half-Life, and Half-Life
|
|
||||||
# 2 (Source Engine) RCon (Remote Console) protocols.
|
|
||||||
#
|
|
||||||
# Version:: 0.2.0
|
|
||||||
# Author:: Erik Hollensbe <erik@hollensbe.org>
|
|
||||||
# License:: BSD
|
|
||||||
# Contact:: erik@hollensbe.org
|
|
||||||
# Copyright:: Copyright (c) 2005-2006 Erik Hollensbe
|
|
||||||
#
|
|
||||||
# The relevant modules to query RCon are in the RCon::Query namespace,
|
|
||||||
# under RCon::Query::Original (for Quake 1/2/3 and Half-Life), and
|
|
||||||
# RCon::Query::Source (for HL2 and CS: Source, and other Source Engine
|
|
||||||
# games). The RCon::Packet namespace is used to manage complex packet
|
|
||||||
# structures if required. The Original protocol does not require
|
|
||||||
# this, but Source does.
|
|
||||||
#
|
|
||||||
# Usage is fairly simple:
|
|
||||||
#
|
|
||||||
# # Note: Other classes have different constructors
|
|
||||||
#
|
|
||||||
# rcon = RCon::Query::Source.new("10.0.0.1", 27015)
|
|
||||||
#
|
|
||||||
# rcon.auth("foobar") # source only
|
|
||||||
#
|
|
||||||
# rcon.command("mp_friendlyfire") => "mp_friendlyfire = 1"
|
|
||||||
#
|
|
||||||
# rcon.cvar("mp_friendlyfire") => 1
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
#
|
|
||||||
# The compilation of software known as rcon.rb is distributed under the
|
|
||||||
# following terms:
|
|
||||||
# Copyright (C) 2005-2006 Erik Hollensbe. All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source form, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
||||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
||||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
# SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
#++
|
|
||||||
|
|
||||||
|
|
||||||
class RCon
|
|
||||||
class Packet
|
|
||||||
# placeholder so ruby doesn't bitch
|
|
||||||
end
|
|
||||||
class Query
|
|
||||||
|
|
||||||
#
|
|
||||||
# Convenience method to scrape input from cvar output and return that data.
|
|
||||||
# Returns integers as a numeric type if possible.
|
|
||||||
#
|
|
||||||
# ex: rcon.cvar("mp_friendlyfire") => 1
|
|
||||||
#
|
|
||||||
|
|
||||||
def cvar(cvar_name)
|
|
||||||
response = command(cvar_name)
|
|
||||||
match = /^.+?\s(?:is|=)\s"([^"]+)".*$/.match response
|
|
||||||
match = match[1]
|
|
||||||
if /\D/.match match
|
|
||||||
return match
|
|
||||||
else
|
|
||||||
return match.to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# RCon::Packet::Source generates a packet structure useful for
|
|
||||||
# RCon::Query::Source protocol queries.
|
|
||||||
#
|
|
||||||
# This class is primarily used internally, but is available if you
|
|
||||||
# want to do something more advanced with the Source RCon
|
|
||||||
# protocol.
|
|
||||||
#
|
|
||||||
# Use at your own risk.
|
|
||||||
#
|
|
||||||
|
|
||||||
class RCon::Packet::Source
|
|
||||||
# execution command
|
|
||||||
COMMAND_EXEC = 2
|
|
||||||
# auth command
|
|
||||||
COMMAND_AUTH = 3
|
|
||||||
# auth response
|
|
||||||
RESPONSE_AUTH = 2
|
|
||||||
# normal response
|
|
||||||
RESPONSE_NORM = 0
|
|
||||||
# packet trailer
|
|
||||||
TRAILER = "\x00\x00"
|
|
||||||
|
|
||||||
# size of the packet (10 bytes for header + string1 length)
|
|
||||||
attr_accessor :packet_size
|
|
||||||
# Request Identifier, used in managing multiple requests at once
|
|
||||||
attr_accessor :request_id
|
|
||||||
# Type of command, normally COMMAND_AUTH or COMMAND_EXEC. In response packets, RESPONSE_AUTH or RESPONSE_NORM
|
|
||||||
attr_accessor :command_type
|
|
||||||
# First string, the only used one in the protocol, contains
|
|
||||||
# commands and responses. Null terminated.
|
|
||||||
attr_accessor :string1
|
|
||||||
# Second string, unused by the protocol. Null terminated.
|
|
||||||
attr_accessor :string2
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generate a command packet to be sent to an already
|
|
||||||
# authenticated RCon connection. Takes the command as an
|
|
||||||
# argument.
|
|
||||||
#
|
|
||||||
def command(string)
|
|
||||||
@request_id = rand(1000)
|
|
||||||
@string1 = string
|
|
||||||
@string2 = TRAILER
|
|
||||||
@command_type = COMMAND_EXEC
|
|
||||||
|
|
||||||
@packet_size = build_packet.length
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generate an authentication packet to be sent to a newly
|
|
||||||
# started RCon connection. Takes the RCon password as an
|
|
||||||
# argument.
|
|
||||||
#
|
|
||||||
def auth(string)
|
|
||||||
@request_id = rand(1000)
|
|
||||||
@string1 = string
|
|
||||||
@string2 = TRAILER
|
|
||||||
@command_type = COMMAND_AUTH
|
|
||||||
|
|
||||||
@packet_size = build_packet.length
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Builds a packet ready to deliver, without the size prepended.
|
|
||||||
# Used to calculate the packet size, use #to_s to get the packet
|
|
||||||
# that srcds actually needs.
|
|
||||||
#
|
|
||||||
def build_packet
|
|
||||||
return [@request_id, @command_type, @string1, @string2].pack("VVa#{@string1.length}a2")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a string representation of the packet, useful for
|
|
||||||
# sending and debugging. This include the packet size.
|
|
||||||
def to_s
|
|
||||||
packet = build_packet
|
|
||||||
@packet_size = packet.length
|
|
||||||
return [@packet_size].pack("V") + packet
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# RCon::Query::Original queries Quake 1/2/3 and Half-Life servers
|
|
||||||
# with the rcon protocol. This protocol travels over UDP to the
|
|
||||||
# game server port, and requires an initial authentication step,
|
|
||||||
# the information of which is provided at construction time.
|
|
||||||
#
|
|
||||||
# Some of the work here (namely the RCon packet structure) was taken
|
|
||||||
# from the KKRcon code, which is written in perl.
|
|
||||||
#
|
|
||||||
# One query per authentication is allowed.
|
|
||||||
#
|
|
||||||
|
|
||||||
class RCon::Query::Original < RCon::Query
|
|
||||||
# HLDS-Based Servers
|
|
||||||
HLDS = "l"
|
|
||||||
# QuakeWorld/Quake 1 Servers
|
|
||||||
QUAKEWORLD = "n"
|
|
||||||
# Quake 2/3 Servers
|
|
||||||
NEWQUAKE = ""
|
|
||||||
|
|
||||||
# Request to be sent to server
|
|
||||||
attr_reader :request
|
|
||||||
# Response from server
|
|
||||||
attr_reader :response
|
|
||||||
# Challenge ID (served by server-side of connection)
|
|
||||||
attr_reader :challenge_id
|
|
||||||
# UDPSocket object
|
|
||||||
attr_reader :socket
|
|
||||||
# Host of connection
|
|
||||||
attr_reader :host
|
|
||||||
# Port of connection
|
|
||||||
attr_reader :port
|
|
||||||
# RCon password
|
|
||||||
attr_reader :password
|
|
||||||
# type of server
|
|
||||||
attr_reader :server_type
|
|
||||||
|
|
||||||
#
|
|
||||||
# Creates a RCon::Query::Original object for use.
|
|
||||||
#
|
|
||||||
# The type (the default of which is HLDS), has multiple possible
|
|
||||||
# values:
|
|
||||||
#
|
|
||||||
# HLDS - Half Life 1 (will not work with older versions of HLDS)
|
|
||||||
#
|
|
||||||
# QUAKEWORLD - QuakeWorld/Quake 1
|
|
||||||
#
|
|
||||||
# NEWQUAKE - Quake 2/3 (and many derivatives)
|
|
||||||
#
|
|
||||||
|
|
||||||
def initialize(host, port, password, type=HLDS)
|
|
||||||
@host = host
|
|
||||||
@port = port
|
|
||||||
@password = password
|
|
||||||
@server_type = type
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sends a request given as the argument, and returns the
|
|
||||||
# response as a string.
|
|
||||||
#
|
|
||||||
def command(request)
|
|
||||||
@request = request
|
|
||||||
@challenge_id = nil
|
|
||||||
|
|
||||||
establish_connection
|
|
||||||
|
|
||||||
@socket.print "\xFF" * 4 + "challenge rcon\n\x00"
|
|
||||||
|
|
||||||
tmp = retrieve_socket_data
|
|
||||||
challenge_id = /challenge rcon (\d+)/.match tmp
|
|
||||||
if challenge_id
|
|
||||||
@challenge_id = challenge_id[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
if @challenge_id.nil?
|
|
||||||
raise RCon::NetworkException.new("RCon challenge ID never returned: wrong rcon password?")
|
|
||||||
end
|
|
||||||
|
|
||||||
@socket.print "\xFF" * 4 + "rcon #{@challenge_id} \"#{@password}\" #{@request}\n\x00"
|
|
||||||
@response = retrieve_socket_data
|
|
||||||
|
|
||||||
@response.sub!(/^\xFF\xFF\xFF\xFF#{@server_type}/, "")
|
|
||||||
@response.sub!(/\x00+$/, "")
|
|
||||||
|
|
||||||
return @response
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Disconnects the RCon connection.
|
|
||||||
#
|
|
||||||
def disconnect
|
|
||||||
if @socket
|
|
||||||
@socket.close
|
|
||||||
@socket = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
#
|
|
||||||
# Establishes the connection.
|
|
||||||
#
|
|
||||||
def establish_connection
|
|
||||||
if @socket.nil?
|
|
||||||
@socket = UDPSocket.new
|
|
||||||
@socket.connect(@host, @port)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generic method to pull data from the socket.
|
|
||||||
#
|
|
||||||
|
|
||||||
def retrieve_socket_data
|
|
||||||
return "" if @socket.nil?
|
|
||||||
|
|
||||||
retval = ""
|
|
||||||
loop do
|
|
||||||
break unless IO.select([@socket], nil, nil, 10)
|
|
||||||
packet = @socket.recv(8192)
|
|
||||||
retval << packet
|
|
||||||
break if packet.length < 8192
|
|
||||||
end
|
|
||||||
|
|
||||||
return retval
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# RCon::Query::Source sends queries to a "Source" Engine server,
|
|
||||||
# such as Half-Life 2: Deathmatch, Counter-Strike: Source, or Day
|
|
||||||
# of Defeat: Source.
|
|
||||||
#
|
|
||||||
# Note that one authentication packet needs to be sent to send
|
|
||||||
# multiple commands. Sending multiple authentication packets may
|
|
||||||
# damage the current connection and require it to be reset.
|
|
||||||
#
|
|
||||||
# Note: If the attribute 'return_packets' is set to true, the full
|
|
||||||
# RCon::Packet::Source object is returned, instead of just a string
|
|
||||||
# with the headers stripped. Useful for debugging.
|
|
||||||
#
|
|
||||||
|
|
||||||
class RCon::Query::Source < RCon::Query
|
|
||||||
# RCon::Packet::Source object that was sent as a result of the last query
|
|
||||||
attr_reader :packet
|
|
||||||
# TCPSocket object
|
|
||||||
attr_reader :socket
|
|
||||||
# Host of connection
|
|
||||||
attr_reader :host
|
|
||||||
# Port of connection
|
|
||||||
attr_reader :port
|
|
||||||
# Authentication Status
|
|
||||||
attr_reader :authed
|
|
||||||
# return full packet, or just data?
|
|
||||||
attr_accessor :return_packets
|
|
||||||
|
|
||||||
#
|
|
||||||
# Given a host and a port (dotted-quad or hostname OK), creates
|
|
||||||
# a RCon::Query::Source object. Note that this will still
|
|
||||||
# require an authentication packet (see the auth() method)
|
|
||||||
# before commands can be sent.
|
|
||||||
#
|
|
||||||
|
|
||||||
def initialize(host, port)
|
|
||||||
@host = host
|
|
||||||
@port = port
|
|
||||||
@socket = nil
|
|
||||||
@packet = nil
|
|
||||||
@authed = false
|
|
||||||
@return_packets = false
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# See RCon::Query#cvar.
|
|
||||||
#
|
|
||||||
|
|
||||||
def cvar(cvar_name)
|
|
||||||
return_packets = @return_packets
|
|
||||||
@return_packets = false
|
|
||||||
response = super
|
|
||||||
@return_packets = return_packets
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sends a RCon command to the server. May be used multiple times
|
|
||||||
# after an authentication is successful.
|
|
||||||
#
|
|
||||||
# See the class-level documentation on the 'return_packet' attribute
|
|
||||||
# for return values. The default is to return a string containing
|
|
||||||
# the response.
|
|
||||||
#
|
|
||||||
|
|
||||||
def command(command)
|
|
||||||
|
|
||||||
if ! @authed
|
|
||||||
raise RCon::NetworkException.new("You must authenticate the connection successfully before sending commands.")
|
|
||||||
end
|
|
||||||
|
|
||||||
@packet = RCon::Packet::Source.new
|
|
||||||
@packet.command(command)
|
|
||||||
|
|
||||||
@socket.print @packet.to_s
|
|
||||||
rpacket = build_response_packet
|
|
||||||
|
|
||||||
if rpacket.command_type != RCon::Packet::Source::RESPONSE_NORM
|
|
||||||
raise RCon::NetworkException.new("error sending command: #{rpacket.command_type}")
|
|
||||||
end
|
|
||||||
|
|
||||||
if @return_packets
|
|
||||||
return rpacket
|
|
||||||
else
|
|
||||||
return rpacket.string1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Requests authentication from the RCon server, given a
|
|
||||||
# password. Is only expected to be used once.
|
|
||||||
#
|
|
||||||
# See the class-level documentation on the 'return_packet' attribute
|
|
||||||
# for return values. The default is to return a true value if auth
|
|
||||||
# succeeded.
|
|
||||||
#
|
|
||||||
|
|
||||||
def auth(password)
|
|
||||||
establish_connection
|
|
||||||
|
|
||||||
@packet = RCon::Packet::Source.new
|
|
||||||
@packet.auth(password)
|
|
||||||
|
|
||||||
@socket.print @packet.to_s
|
|
||||||
# on auth, one junk packet is sent
|
|
||||||
rpacket = nil
|
|
||||||
2.times { rpacket = build_response_packet }
|
|
||||||
|
|
||||||
if rpacket.command_type != RCon::Packet::Source::RESPONSE_AUTH
|
|
||||||
raise RCon::NetworkException.new("error authenticating: #{rpacket.command_type}")
|
|
||||||
end
|
|
||||||
|
|
||||||
@authed = true
|
|
||||||
if @return_packets
|
|
||||||
return rpacket
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias_method :authenticate, :auth
|
|
||||||
|
|
||||||
#
|
|
||||||
# Disconnects from the Source server.
|
|
||||||
#
|
|
||||||
|
|
||||||
def disconnect
|
|
||||||
if @socket
|
|
||||||
@socket.close
|
|
||||||
@socket = nil
|
|
||||||
@authed = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
#
|
|
||||||
# Builds a RCon::Packet::Source packet based on the response
|
|
||||||
# given by the server.
|
|
||||||
#
|
|
||||||
def build_response_packet
|
|
||||||
rpacket = RCon::Packet::Source.new
|
|
||||||
total_size = 0
|
|
||||||
request_id = 0
|
|
||||||
type = 0
|
|
||||||
response = ""
|
|
||||||
message = ""
|
|
||||||
|
|
||||||
|
|
||||||
loop do
|
|
||||||
break unless IO.select([@socket], nil, nil, 10)
|
|
||||||
|
|
||||||
#
|
|
||||||
# TODO: clean this up - read everything and then unpack.
|
|
||||||
#
|
|
||||||
|
|
||||||
tmp = @socket.recv(14)
|
|
||||||
if tmp.nil?
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
size, request_id, type, message = tmp.unpack("VVVa*")
|
|
||||||
total_size += size
|
|
||||||
|
|
||||||
# special case for authentication
|
|
||||||
break if message.sub!(/\x00\x00$/, "")
|
|
||||||
|
|
||||||
response << message
|
|
||||||
|
|
||||||
# the 'size - 10' here accounts for the fact that we've snarfed 14 bytes,
|
|
||||||
# the size (which is 4 bytes) is not counted, yet represents the rest
|
|
||||||
# of the packet (which we have already taken 10 bytes from)
|
|
||||||
|
|
||||||
tmp = @socket.recv(size - 10)
|
|
||||||
response << tmp
|
|
||||||
response.sub!(/\x00\x00$/, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
rpacket.packet_size = total_size
|
|
||||||
rpacket.request_id = request_id
|
|
||||||
rpacket.command_type = type
|
|
||||||
|
|
||||||
# strip nulls (this is actually the end of string1 and string2)
|
|
||||||
rpacket.string1 = response.sub(/\x00\x00$/, "")
|
|
||||||
return rpacket
|
|
||||||
end
|
|
||||||
|
|
||||||
# establishes a connection to the server.
|
|
||||||
def establish_connection
|
|
||||||
if @socket.nil?
|
|
||||||
@socket = TCPSocket.new(@host, @port)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
# Exception class for network errors
|
|
||||||
class RCon::NetworkException < Exception
|
|
||||||
end
|
|
13
vendor/plugins/rcon/rcon.gemspec
vendored
13
vendor/plugins/rcon/rcon.gemspec
vendored
|
@ -1,13 +0,0 @@
|
||||||
spec = Gem::Specification.new
|
|
||||||
spec.name = "rcon"
|
|
||||||
spec.version = "0.2.1"
|
|
||||||
spec.author = "Erik Hollensbe"
|
|
||||||
spec.email = "erik@hollensbe.org"
|
|
||||||
spec.summary = "Ruby class to work with Quake 1/2/3, Half-Life and Source Engine rcon (remote console)"
|
|
||||||
spec.has_rdoc = true
|
|
||||||
spec.autorequire = "rcon"
|
|
||||||
spec.bindir = 'bin'
|
|
||||||
spec.executables << 'rcontool'
|
|
||||||
spec.add_dependency('ip', '>= 0.2.1')
|
|
||||||
spec.files = Dir['lib/rcon.rb'] + Dir['bin/rcontool']
|
|
||||||
spec.rubyforge_project = 'rcon'
|
|
1360
vendor/plugins/rcon/setup.rb
vendored
1360
vendor/plugins/rcon/setup.rb
vendored
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue