KKrcon.pm 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package KKrcon;
  2. #
  3. # KKrcon Perl Module - execute commands on a remote Half-Life server using Rcon.
  4. # http://kkrcon.sourceforge.net
  5. #
  6. # Synopsis:
  7. #
  8. # use KKrcon;
  9. # $rcon = new KKrcon(Password=>PASSWORD, [Host=>HOST], [Port=>PORT], [Type=>"new"|"old"]);
  10. # $result = $rcon->execute(COMMAND);
  11. # %players = $rcon->getPlayers();
  12. # $player = $rcon->getPlayer(USERID);
  13. #
  14. # Copyright (C) 2000, 2001 Rod May
  15. #
  16. # This program is free software; you can redistribute it and/or
  17. # modify it under the terms of the GNU General Public License
  18. # as published by the Free Software Foundation; either version 2
  19. # of the License, or (at your option) any later version.
  20. #
  21. # This program is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. # You should have received a copy of the GNU General Public License
  27. # along with this program; if not, write to the Free Software
  28. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  29. #
  30. use Socket;
  31. use Sys::Hostname;
  32. # Release version number
  33. $VERSION = "2.11";
  34. ##
  35. ## Main
  36. ##
  37. #
  38. # Constructor
  39. #
  40. sub new
  41. {
  42. my $class_name = shift;
  43. my %params = @_;
  44. my $self = {};
  45. bless($self, $class_name);
  46. my %server_types = (new=>1, old=>2);
  47. # Check parameters
  48. $params{"Host"} = "127.0.0.1" unless($params{"Host"});
  49. $params{"Port"} = 27015 unless($params{"Port"});
  50. $params{"Type"} = "new" unless($params{"Type"});
  51. # Initialise properties
  52. $self->{"rcon_password"} = $params{"Password"}
  53. or die("KKrcon: a Password is required\n");
  54. $self->{"server_host"} = $params{"Host"};
  55. $self->{"server_port"} = int($params{"Port"})
  56. or die("KKrcon: invalid Port \"" . $params{"Port"} . "\"\n");
  57. $self->{"server_type"} = ($server_types{$params{"Type"}} || 1);
  58. $self->{"error"} = "";
  59. # Set up socket parameters
  60. $self->{"_ipaddr"} = gethostbyname($self->{"server_host"})
  61. or die("KKrcon: could not resolve Host \"" . $self->{"server_host"} . "\"\n");
  62. return $self;
  63. }
  64. #
  65. # Execute an Rcon command and return the response
  66. #
  67. sub execute
  68. {
  69. my ($self, $command) = @_;
  70. my $msg;
  71. my $ans;
  72. if ($self->{"server_type"} == 1)
  73. {
  74. # version x.1.0.6+ HL server
  75. $msg = "\xFF\xFF\xFF\xFFchallenge rcon\n\0";
  76. $ans = $self->_sendrecv($msg);
  77. if ($ans =~ /challenge +rcon +(\d+)/)
  78. {
  79. $msg = "\xFF\xFF\xFF\xFFrcon $1 \"" . $self->{"rcon_password"} . "\" $command\0";
  80. $ans = $self->_sendrecv($msg);
  81. }
  82. elsif (!$self->error())
  83. {
  84. $ans = "";
  85. $self->{"error"} = "No challenge response";
  86. }
  87. }
  88. else
  89. {
  90. # QW/Q2/Q3 or old HL server
  91. $msg = "\xFF\xFF\xFF\xFFrcon " . $self->{"rcon_password"} . " $command\n\0";
  92. $ans = $self->_sendrecv($msg);
  93. }
  94. if ($ans =~ /bad rcon_password/i)
  95. {
  96. $self->{"error"} = "Bad Password";
  97. }
  98. return $ans;
  99. }
  100. sub _sendrecv
  101. {
  102. my ($self, $msg) = @_;
  103. my $host = $self->{"server_host"};
  104. my $port = $self->{"server_port"};
  105. my $ipaddr = $self->{"_ipaddr"};
  106. # Open socket
  107. socket(RCON, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die("KKrcon: socket: $!\n");
  108. my $hispaddr = sockaddr_in($port, $ipaddr);
  109. unless(defined(send(RCON, $msg, 0, $hispaddr)))
  110. {
  111. die("KKrcon: send $ip:$port : $!");
  112. }
  113. my $rin;
  114. vec($rin, fileno(RCON), 1) = 1;
  115. my $ans;
  116. if (select($rin, undef, undef, 10.0)) {
  117. $hispaddr = recv(RCON, $ans, 8192, 0);
  118. if (defined($ans)) {
  119. $ans =~ s/^\xFF\xFF\xFF\xFFprint\n//; # CoD2 response
  120. $ans =~ s/\x00+$//; # trailing crap
  121. $ans =~ s/^\xFF\xFF\xFF\xFFl//; # HL response
  122. $ans =~ s/^\xFF\xFF\xFF\xFFn//; # QW response
  123. $ans =~ s/^\xFF\xFF\xFF\xFF//; # Q2/Q3 response
  124. $ans =~ s/^\xFE\xFF\xFF\xFF.....//; # old HL bug/feature
  125. if (length($ans) > 512) {
  126. my $tmp;
  127. my @explode;
  128. while (select($rin, undef, undef, 0.05)) {
  129. @explode = split(/\n/, $ans);
  130. $explode[$#explode] =~ s/^ //;
  131. $explode[$#explode] = 'X' . $explode[$#explode];
  132. $ans = join("\n", @explode);
  133. $hispaddr = recv(RCON, $tmp, 8192, 0);
  134. if (defined($tmp)) {
  135. $tmp =~ s/^\xFF\xFF\xFF\xFFprint\n//; # CoD2 response
  136. $tmp =~ s/\x00+$//; # trailing crap
  137. $tmp =~ s/^\xFF\xFF\xFF\xFFl//; # HL response
  138. $tmp =~ s/^\xFF\xFF\xFF\xFFn//; # QW response
  139. $tmp =~ s/^\xFF\xFF\xFF\xFF//; # Q2/Q3 response
  140. $tmp =~ s/^\xFE\xFF\xFF\xFF.....//; # old HL bug/feature
  141. $ans .= $tmp;
  142. }
  143. }
  144. }
  145. }
  146. }
  147. # Close socket
  148. close(RCON);
  149. if (!defined($ans)) {
  150. $ans = "";
  151. $self->{"error"} = "Rcon timeout";
  152. }
  153. return $ans;
  154. }
  155. #
  156. # Get error message
  157. #
  158. sub error
  159. {
  160. my ($self) = @_;
  161. return $self->{"error"};
  162. }
  163. #
  164. # Parse "status" command output into player information
  165. #
  166. sub getPlayers
  167. {
  168. my ($self) = @_;
  169. my $status = $self->execute("status");
  170. my @lines = split(/[\r\n]+/, $status);
  171. my %players;
  172. foreach $line (@lines)
  173. {
  174. if ($line =~ /^\#[\s\d]\d\s+
  175. (.+)\s+ # name
  176. (\d+)\s+ # userid
  177. (\d+)\s+ # wonid
  178. ([\d-]+)\s+ # frags
  179. ([\d:]+)\s+ # time
  180. (\d+)\s+ # ping
  181. (\d+)\s+ # loss
  182. (\S+) # addr
  183. $/x)
  184. {
  185. my $name = $1;
  186. my $userid = $2;
  187. my $wonid = $3;
  188. my $frags = $4;
  189. my $time = $5;
  190. my $ping = $6;
  191. my $loss = $7;
  192. my $address = $8;
  193. $players{$userid} = {
  194. "Name" => $name,
  195. "UserID" => $userid,
  196. "WONID" => $wonid,
  197. "Frags" => $frags,
  198. "Time" => $time,
  199. "Ping" => $ping,
  200. "Loss" => $loss,
  201. "Address" => $address
  202. };
  203. }
  204. }
  205. return %players;
  206. }
  207. #
  208. # Get information about a player by userID
  209. #
  210. sub getPlayer
  211. {
  212. my ($self, $userid) = @_;
  213. my %players = $self->getPlayers();
  214. if (defined($players{$userid}))
  215. {
  216. return $players{$userid};
  217. }
  218. else
  219. {
  220. $self->{"error"} = "No such player # $userid";
  221. return 0;
  222. }
  223. }
  224. 1;
  225. # end