| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- # HL2 - Perl extension Half-Life 2 (Source) engine Rcon interface
- #
- # $Id:$
- #
- package HL2;
- use strict;
- use warnings;
- use IO::Socket;
- use IO::Select;
- # release version
- our $VERSION = "0.05";
- # constants for command type
- sub CMD { 2 }
- sub AUTH { 3 }
- # create class
- sub new {
- my $class = shift;
- # create object with defaults
- my $self = {
- hostname => undef,
- port => 27015,
- password => undef,
- timeout => 5,
- connected => 0,
- authenticated => 0,
- socket => undef,
- sequence => 0,
- };
- # create object
- bless($self, $class);
- # initialize class instances
- $self->init();
- # parse constructor args
- while (my ($key, $val) = splice(@_, 0, 2)) {
- $key = lc($key);
- if ($key eq "hostname") { $self->hostname($val) }
- elsif ($key eq "port") { $self->port($val) }
- elsif ($key eq "password") { $self->password($val) }
- elsif ($key eq "timeout") { $self->timeout($val) }
- else { print STDERR "Unknown attribute: $key\n" }
- }
- return $self;
- }
- # initialize class instances
- sub init {
- my $self = shift;
- my $class = ref($self);
- # manipulate symbol table.. gotta love perl
- no strict "refs";
- no warnings;
- foreach my $instance (keys %$self) {
- *{"${class}::${instance}"} = sub {
- my $self = shift;
- my $value = shift;
- my $ref = \$self->{$instance};
- if (defined $value) {
- $$ref = $value;
- return $self;
- } else {
- return $$ref;
- }
- };
- }
- }
- # run a command and return its response
- sub run {
- my $self = shift;
- my $command = shift;
- if (!$self->connected()) {
- $self->connect();
- }
- if (!$self->authenticated()) {
- $self->authenticate();
- }
- my $socket = $self->socket();
- if($socket->connected)
- {
- print $socket $self->packet(CMD, $command);
- return $self->response();
- }
-
- return;
- }
- # create tcp socket
- sub connect {
- my $self = shift;
- my $socket = IO::Socket::INET->new(
- PeerAddr => $self->hostname(),
- PeerPort => $self->port(),
- Timeout => $self->timeout(),
- Proto => "tcp",
- Type => SOCK_STREAM,
- ) || die "Failed to connect: $!\n";
- $self->socket($socket);
- $self->connected(1);
- }
- # authenticate rcon session
- sub authenticate {
- my $self = shift;
- # send authentication packet to server
- my $socket = $self->socket();
- print $socket $self->packet(AUTH, $self->password());
- # auth response sends back an empty packet first
- $self->response();
- $self->response();
- $self->authenticated(1);
- }
- ######################
- # PROTOCOL FUNCTIONS #
- ######################
- # rcon command protocol:
- # (V)[size] (V)[requestID] (V)[command] (0)[string1] (0)[string2]
- #
- # rcon response protocol:
- # (V)[size] (V)[requestID] (V)[responseID] (0)[string1] (0)[string2]
- #
- # V = a 32-bit unsigned long int, little-endian (VAX/Intel)
- # 0 = null-terminated string
- #
- # NOTE: string2 appears unused, so our functions ignore it
- # create a packet of type (AUTH or CMD)
- sub packet {
- my $self = shift;
- my $type = shift;
- my $payload = shift;
- # sequence increments, but auth
- # packet is 2.. no idea why that is,
- # but tcpdump does not lie
- my $sequence;
- if ($type == AUTH) {
- $sequence = 2;
- } else {
- $sequence = $self->sequence();
- # increment for next use
- $self->sequence($sequence + 1);
- }
- my $packet = pack("VV", $sequence, $type) . "$payload\x00\x00";
- $packet = pack("V", length($packet)) . $packet;
- return $packet;
- }
- # receive packet
- sub response {
- my $self = shift;
- my $payload = $self->read();
- # remove protocol cruft and null terminators
- $payload =~ s/\x00{2}$//;
- return $payload;
- }
- # read length of bytes from socket with timeout
- sub read {
- my $self = shift;
- my $length = shift;
- my $socket = $self->socket();
- my $timeout = $self->timeout();
- my $select = IO::Select->new($socket);
- my $reply = "";
- my $buffer;
- my ($size, $request_id, $command_response, $data);
- while ($select->can_read(0.5)) {
- $socket->recv($buffer, 4, MSG_PEEK);
- $size = unpack("V", $buffer);
- last if (!defined($size));
- $socket->recv($buffer, $size+4, MSG_WAITALL);
- ($size, $request_id, $command_response, $data) =
- unpack('VVVZ*x', $buffer);
- $reply .= "$data";
- }
- return $reply;
- }
- 1;
- __END__
- =head1 NAME
- HL2 - Perl extension Half-Life 2 (Source) engine Rcon interface
- =head1 SYNOPSIS
- use HL2;
- my $rcon = HL2->new(
- hostname => "insub.org",
- password => "yourpass",
- timeout => 3,
- );
- print $rcon->run("status");
- $rcon->run("changelevel de_dust");
- =head1 DESCRIPTION
- Use this module to send "rcon" (remote control) commands to a
- Source server, such as Counter-Strike Source.
- =head1 METHODS
- =over 4
- =item $rcon = HL2->new()
- Create a new rcon object. You can specify the hostname,
- password and/or timeout in the constructor, or use the class
- methods to change them (see SYNOPSIS).
- =item $rcon->authenticated()
- Returns true if session has succesfully authenticated.
- =item $rcon->password()
- Returns current password, or sets it. Note that setting
- this after authentication will not have any effect unless
- you reconnect with $rcon->authenticated(0).
- =item $rcon->hostname()
- Returns current hostname, or sets it.
- =item $rcon->port()
- Returns current port, or sets it. Defaults to 27015.
- =item $rcon->sequence()
- Returns the current command sequence. This starts
- at 0 and increases with each call.
- =item $rcon->socket()
- Returns the IO::Socket object for the session or
- creates a new one if none exists.
- =item $rcon->timeout()
- Returns the TCP response timeout, or sets it. Defaults
- to 5.
- =item $rcon->connect()
- Connects to remote server.
- =item $packet = $rcon->packet($type, $payload)
- Creats a packet to send to the remote server.
- Type should be either CMD or AUTH, e.g.:
- print $socket $rcon->packet(AUTH, $rcon->password())
- =item $rcon->authenticate()
- Authenticates with the rcon server. This is done automatically
- when you try to run a command.
- =item $response = $rcon->run($command)
- Runs a command on the remote server and returns its response
- =item $response = $rcon->response()
- Reads a response packet from the server. This is called
- authomatically when you use run() so you shouldn't need to
- use this.
- =back
- =head1 CAVEATS
- This module DOES NOT DO ANY COMMAND VALIDATION. You are responsible for
- sending sane commands to the server. If you use this with CGI that allows
- internet users to submit console commands, you MUST taint-check this. Users
- with RCON access can send anything to the console. I highly recommend that you
- restrict what console commands a user can send.
- =head1 BUGS
- As of this writing, there are some bugs with the rcon server itself.
- One such bug is that some output goes to the console instead of to
- the rcon client. For example, the command "listid" causes the list
- of banned users to spew to the physical console instead of back to
- the rcon client, making it effectively useless. If you are not getting
- back a response you expected, please verify that it's not going to
- the console (run srcds in screen so you can access it) before submitting
- a bug report to me about it. Or better yet, submit a bug report to Valve.
- Authentication validation is currently unsupported.
- =head1 SEE ALSO
- http://gruntle.org/projects/
- http://insub.org/cs/
- =head1 AUTHOR
- Chris Jones, E<lt>[email protected]<gt>
- =head1 COPYRIGHT AND LICENSE
- Copyright (C) 2004 by Chris Jones
- This library is free software; you can redistribute it and/or modify
- it under the same terms as Perl itself, either Perl version 5.8.5 or,
- at your option, any later version of Perl 5 you may have available.
- =cut
|