HL2.pm 7.3 KB

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