HL2.pm 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. # HL2 - Perl extension Half-Life 2 (Source) engine Rcon interface
  2. #
  3. # $Id:$
  4. #
  5. package HL2;
  6. use strict;
  7. use warnings;
  8. use IO::Socket;
  9. use IO::Select;
  10. # release version
  11. our $VERSION = "0.05";
  12. # constants for command type
  13. sub CMD { 2 }
  14. sub AUTH { 3 }
  15. # create class
  16. sub new {
  17. my $class = shift;
  18. # create object with defaults
  19. my $self = {
  20. hostname => undef,
  21. port => 27015,
  22. password => undef,
  23. timeout => 5,
  24. connected => 0,
  25. authenticated => 0,
  26. socket => undef,
  27. sequence => 0,
  28. };
  29. # create object
  30. bless($self, $class);
  31. # initialize class instances
  32. $self->init();
  33. # parse constructor args
  34. while (my ($key, $val) = splice(@_, 0, 2)) {
  35. $key = lc($key);
  36. if ($key eq "hostname") { $self->hostname($val) }
  37. elsif ($key eq "port") { $self->port($val) }
  38. elsif ($key eq "password") { $self->password($val) }
  39. elsif ($key eq "timeout") { $self->timeout($val) }
  40. else { print STDERR "Unknown attribute: $key\n" }
  41. }
  42. return $self;
  43. }
  44. # initialize class instances
  45. sub init {
  46. my $self = shift;
  47. my $class = ref($self);
  48. # manipulate symbol table.. gotta love perl
  49. no strict "refs";
  50. no warnings;
  51. foreach my $instance (keys %$self) {
  52. *{"${class}::${instance}"} = sub {
  53. my $self = shift;
  54. my $value = shift;
  55. my $ref = \$self->{$instance};
  56. if (defined $value) {
  57. $$ref = $value;
  58. return $self;
  59. } else {
  60. return $$ref;
  61. }
  62. };
  63. }
  64. }
  65. # run a command and return its response
  66. sub run {
  67. my $self = shift;
  68. my $command = shift;
  69. if (!$self->connected()) {
  70. $self->connect();
  71. }
  72. if (!$self->authenticated()) {
  73. $self->authenticate();
  74. }
  75. my $socket = $self->socket();
  76. if($socket->connected)
  77. {
  78. print $socket $self->packet(CMD, $command);
  79. return $self->response();
  80. }
  81. return;
  82. }
  83. # create tcp socket
  84. sub connect {
  85. my $self = shift;
  86. my $socket = IO::Socket::INET->new(
  87. PeerAddr => $self->hostname(),
  88. PeerPort => $self->port(),
  89. Timeout => $self->timeout(),
  90. Proto => "tcp",
  91. Type => SOCK_STREAM,
  92. ) || die "Failed to connect: $!\n";
  93. $self->socket($socket);
  94. $self->connected(1);
  95. }
  96. # authenticate rcon session
  97. sub authenticate {
  98. my $self = shift;
  99. # send authentication packet to server
  100. my $socket = $self->socket();
  101. print $socket $self->packet(AUTH, $self->password());
  102. # auth response sends back an empty packet first
  103. $self->response();
  104. $self->response();
  105. $self->authenticated(1);
  106. }
  107. ######################
  108. # PROTOCOL FUNCTIONS #
  109. ######################
  110. # rcon command protocol:
  111. # (V)[size] (V)[requestID] (V)[command] (0)[string1] (0)[string2]
  112. #
  113. # rcon response protocol:
  114. # (V)[size] (V)[requestID] (V)[responseID] (0)[string1] (0)[string2]
  115. #
  116. # V = a 32-bit unsigned long int, little-endian (VAX/Intel)
  117. # 0 = null-terminated string
  118. #
  119. # NOTE: string2 appears unused, so our functions ignore it
  120. # create a packet of type (AUTH or CMD)
  121. sub packet {
  122. my $self = shift;
  123. my $type = shift;
  124. my $payload = shift;
  125. # sequence increments, but auth
  126. # packet is 2.. no idea why that is,
  127. # but tcpdump does not lie
  128. my $sequence;
  129. if ($type == AUTH) {
  130. $sequence = 2;
  131. } else {
  132. $sequence = $self->sequence();
  133. # increment for next use
  134. $self->sequence($sequence + 1);
  135. }
  136. my $packet = pack("VV", $sequence, $type) . "$payload\x00\x00";
  137. $packet = pack("V", length($packet)) . $packet;
  138. return $packet;
  139. }
  140. # receive packet
  141. sub response {
  142. my $self = shift;
  143. my $payload = $self->read();
  144. # remove protocol cruft and null terminators
  145. $payload =~ s/\x00{2}$//;
  146. return $payload;
  147. }
  148. # read length of bytes from socket with timeout
  149. sub read {
  150. my $self = shift;
  151. my $length = shift;
  152. my $socket = $self->socket();
  153. my $timeout = $self->timeout();
  154. my $select = IO::Select->new($socket);
  155. my $reply = "";
  156. my $buffer;
  157. my ($size, $request_id, $command_response, $data);
  158. while ($select->can_read(0.5)) {
  159. $socket->recv($buffer, 4, MSG_PEEK);
  160. $size = unpack("V", $buffer);
  161. last if (!defined($size));
  162. $socket->recv($buffer, $size+4, MSG_WAITALL);
  163. ($size, $request_id, $command_response, $data) =
  164. unpack('VVVZ*x', $buffer);
  165. $reply .= "$data";
  166. }
  167. return $reply;
  168. }
  169. 1;
  170. __END__
  171. =head1 NAME
  172. HL2 - Perl extension Half-Life 2 (Source) engine Rcon interface
  173. =head1 SYNOPSIS
  174. use HL2;
  175. my $rcon = HL2->new(
  176. hostname => "insub.org",
  177. password => "yourpass",
  178. timeout => 3,
  179. );
  180. print $rcon->run("status");
  181. $rcon->run("changelevel de_dust");
  182. =head1 DESCRIPTION
  183. Use this module to send "rcon" (remote control) commands to a
  184. Source server, such as Counter-Strike Source.
  185. =head1 METHODS
  186. =over 4
  187. =item $rcon = HL2->new()
  188. Create a new rcon object. You can specify the hostname,
  189. password and/or timeout in the constructor, or use the class
  190. methods to change them (see SYNOPSIS).
  191. =item $rcon->authenticated()
  192. Returns true if session has succesfully authenticated.
  193. =item $rcon->password()
  194. Returns current password, or sets it. Note that setting
  195. this after authentication will not have any effect unless
  196. you reconnect with $rcon->authenticated(0).
  197. =item $rcon->hostname()
  198. Returns current hostname, or sets it.
  199. =item $rcon->port()
  200. Returns current port, or sets it. Defaults to 27015.
  201. =item $rcon->sequence()
  202. Returns the current command sequence. This starts
  203. at 0 and increases with each call.
  204. =item $rcon->socket()
  205. Returns the IO::Socket object for the session or
  206. creates a new one if none exists.
  207. =item $rcon->timeout()
  208. Returns the TCP response timeout, or sets it. Defaults
  209. to 5.
  210. =item $rcon->connect()
  211. Connects to remote server.
  212. =item $packet = $rcon->packet($type, $payload)
  213. Creats a packet to send to the remote server.
  214. Type should be either CMD or AUTH, e.g.:
  215. print $socket $rcon->packet(AUTH, $rcon->password())
  216. =item $rcon->authenticate()
  217. Authenticates with the rcon server. This is done automatically
  218. when you try to run a command.
  219. =item $response = $rcon->run($command)
  220. Runs a command on the remote server and returns its response
  221. =item $response = $rcon->response()
  222. Reads a response packet from the server. This is called
  223. authomatically when you use run() so you shouldn't need to
  224. use this.
  225. =back
  226. =head1 CAVEATS
  227. This module DOES NOT DO ANY COMMAND VALIDATION. You are responsible for
  228. sending sane commands to the server. If you use this with CGI that allows
  229. internet users to submit console commands, you MUST taint-check this. Users
  230. with RCON access can send anything to the console. I highly recommend that you
  231. restrict what console commands a user can send.
  232. =head1 BUGS
  233. As of this writing, there are some bugs with the rcon server itself.
  234. One such bug is that some output goes to the console instead of to
  235. the rcon client. For example, the command "listid" causes the list
  236. of banned users to spew to the physical console instead of back to
  237. the rcon client, making it effectively useless. If you are not getting
  238. back a response you expected, please verify that it's not going to
  239. the console (run srcds in screen so you can access it) before submitting
  240. a bug report to me about it. Or better yet, submit a bug report to Valve.
  241. Authentication validation is currently unsupported.
  242. =head1 SEE ALSO
  243. http://gruntle.org/projects/
  244. http://insub.org/cs/
  245. =head1 AUTHOR
  246. Chris Jones, E<lt>[email protected]<gt>
  247. =head1 COPYRIGHT AND LICENSE
  248. Copyright (C) 2004 by Chris Jones
  249. This library is free software; you can redistribute it and/or modify
  250. it under the same terms as Perl itself, either Perl version 5.8.5 or,
  251. at your option, any later version of Perl 5 you may have available.
  252. =cut