File Coverage

blib/lib/POE/Component/Client/Rcon.pm
Criterion Covered Total %
statement 18 145 12.4
branch 0 66 0.0
condition 0 27 0.0
subroutine 6 19 31.5
pod 2 12 16.6
total 26 269 9.6


line stmt bran cond sub pod time code
1             package POE::Component::Client::Rcon;
2              
3 1     1   742 use strict;
  1         3  
  1         35  
4              
5 1     1   6 use vars qw($VERSION $playerSN);
  1         1  
  1         75  
6             $VERSION = '0.23';
7             $playerSN = 0;
8              
9 1     1   5 use Carp qw(croak);
  1         5  
  1         67  
10 1     1   924 use Socket;
  1         4968  
  1         727  
11 1     1   1243 use Time::HiRes qw(time);
  1         1911  
  1         5  
12 1     1   1041 use POE qw(Session Wheel::SocketFactory);
  1         52124  
  1         8  
13              
14             sub DEBUG () { 0 };
15              
16             sub new {
17 0     0 0   my $type = shift;
18 0           my $self = bless {}, $type;
19              
20 0 0         croak "$type requires an event number of parameters" if @_ % 2;
21              
22 0           my %params = @_;
23              
24 0           my $alias = delete $params{Alias};
25 0 0         $alias = 'rcon' unless defined $alias;
26              
27 0           my $timeout = delete $params{Timeout};
28 0 0 0       $timeout = 15 unless defined $timeout and $timeout >= 0;
29              
30 0           my $retry = delete $params{Retry};
31 0 0 0       $retry = 2 unless defined $retry and $retry >= 0;
32              
33 0           my $bytes = delete $params{Bytes};
34 0 0 0       $bytes = 8192 unless defined $bytes and $bytes > 0;
35              
36 0 0         croak "$type doesn't know these parameters: ", join(', ', sort(keys(%params))) if scalar(keys(%params));
37              
38 0           POE::Session->create(
39             inline_states => {
40             _start => \&_start,
41             rcon => \&rcon,
42             got_socket => \&got_socket,
43             got_message => \&got_message,
44             got_error => \&got_error,
45             got_challenge => \&got_challenge,
46             got_rcon_response => \&got_rcon_response,
47             challenge_timeout => \&challenge_timeout,
48             rcon_timeout => \&rcon_timeout,
49             players => \&players,
50             player_response => \&player_response,
51             player_parse_hl => \&player_parse_hl,
52             player_parse_quake => \&player_parse_quake,
53             },
54             args => [ $timeout, $retry, $alias, $bytes ],
55             );
56              
57 0           return $self;
58             }
59              
60             sub _start {
61 0     0     my ($kernel, $heap, $timeout, $retry, $alias, $bytes) = @_[KERNEL, HEAP, ARG0..ARG3];
62 0           $heap->{timeout} = $timeout;
63 0           $heap->{retry} = $retry;
64 0           $heap->{bytes} = $bytes;
65 0           $kernel->alias_set($alias);
66 0           print STDERR "Rcon object started.\n" if DEBUG;
67             }
68              
69             sub rcon {
70 0     0 1   my ($kernel, $heap, $sender, $type, $ip, $port, $pw, $cmd, $postback) = @_[KERNEL, HEAP, SENDER, ARG0..ARG5];
71 0 0         my ($identifier) = defined($_[ARG6]) ? $_[ARG6] : undef;
72 0           print STDERR "Got $ip:$port with password $pw running command $cmd with postback $postback\n" if DEBUG;
73 0 0         croak "IP address required to execute an Rcon command" unless defined $ip;
74 0 0 0       croak "Port requred to execute an Rcon command" if !defined $port || $port !~ /^\d+$/;
75 0 0 0       croak "Password requires to execute an Rcon command" if !defined $pw || $pw eq '';
76 0 0 0       croak "Command required to execute an Rcon command" if !defined $cmd || $cmd eq '';
77 0 0         croak "Server type was not recognized" unless $type =~ /^(?:qw|q2|q3|oldhl|hl)$/;
78 0           my $challenge = '';
79 0           my $wheel = POE::Wheel::SocketFactory->new(
80             RemoteAddress => $ip,
81             RemotePort => $port,
82             SocketProtocol => 'udp',
83             SuccessEvent => 'got_socket',
84             FailureEvent => 'got_error',
85             );
86 0           $heap->{w_jobs}->{$wheel->ID()} = {
87             ip => $ip,
88             port => $port,
89             pw => $pw,
90             cmd => $cmd,
91             postback => $postback,
92             session => $sender->ID(),
93             wheel => $wheel,
94             identifier => $identifier,
95             type => $type,
96             try => 1, # number of tries...
97             };
98 0           return undef;
99             }
100              
101             sub got_error {
102 0     0 0   my ($operation, $errnum, $errstr, $wheel_id, $heap) = @_[ARG0..ARG3,HEAP];
103 0           warn "Wheel $wheel_id generated $operation error $errnum: $errstr\n";
104 0           delete $heap->{w_jobs}->{$wheel_id}; # shut down that wheel
105             }
106              
107             sub got_socket {
108 0     0 0   my ($kernel, $heap, $socket, $wheelid) = @_[KERNEL, HEAP, ARG0, ARG3];
109              
110 0           $heap->{jobs}->{$socket} = delete($heap->{w_jobs}->{$wheelid});
111 0 0         if($heap->{jobs}->{$socket}->{type} eq 'hl') {
112 0           $kernel->select_read($socket, 'got_challenge');
113 0           send($socket, "\xFF\xFF\xFF\xFFchallenge rcon\n\0", 0);
114 0           $heap->{jobs}->{$socket}->{timer} = $kernel->delay_set('challenge_timeout', $heap->{timeout}, $socket);
115 0           print STDERR "Wheel $wheelid got socket and sent rcon challenge\n" if DEBUG;
116             } else {
117 0           $kernel->yield('got_challenge', $socket);
118             }
119             }
120              
121             sub got_challenge {
122 0     0 0   my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
123              
124 0 0         if($heap->{jobs}->{$socket}->{type} eq 'hl') {
125 0 0         $kernel->alarm_remove($heap->{jobs}->{$socket}->{timer}) if defined $heap->{jobs}->{$socket}->{timer};
126 0           delete($heap->{jobs}->{$socket}->{timer});
127 0           $kernel->select_read($socket);
128 0           recv($socket, my $response = '', 8192, 0);
129            
130 0           print STDERR "got_challenge got the response \"$response\" for $socket\n" if DEBUG;
131 0 0         if($response =~ /challenge +rcon +(\d+)/) {
132 0           $heap->{jobs}->{$socket}->{challenge} = $1;
133 0           $kernel->select_read($socket, 'got_rcon_response');
134 0           send($socket, "\xFF\xFF\xFF\xFFrcon $1 \"" . $heap->{jobs}->{$socket}->{pw} . "\" " . $heap->{jobs}->{$socket}->{cmd} . "\0", 0);
135 0           $heap->{jobs}->{$socket}->{timer} = $kernel->delay_set('rcon_timeout', $heap->{timeout}, $socket);
136 0           print STDERR "Got rcon response and sent rcon command\n" if DEBUG;
137             } else {
138 0           $kernel->post($heap->{jobs}->{$socket}->{session}, $heap->{jobs}->{$socket}->{postback},
139             $heap->{jobs}->{$socket}->{type}.
140             $heap->{jobs}->{$socket}->{ip},
141             $heap->{jobs}->{$socket}->{port},
142             $heap->{jobs}->{$socket}->{cmd},
143             $heap->{jobs}->{$socket}->{identifier},
144             'ERROR: No challenge receieved from server.');
145 0           delete($heap->{jobs}->{$socket});
146             }
147             } else {
148 0           $kernel->select_read($socket, 'got_rcon_response');
149 0           send($socket, "\xFF\xFF\xFF\xFFrcon \"" . $heap->{jobs}->{$socket}->{pw} . "\" " . $heap->{jobs}->{$socket}->{cmd} . "\0", 0);
150 0           $heap->{jobs}->{$socket}->{timer} = $kernel->delay_set('rcon_timeout', $heap->{timeout}, $socket);
151 0           print STDERR "Got socket and sent rcon command\n" if DEBUG;
152             }
153             }
154              
155             sub got_rcon_response {
156 0     0 0   my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
157              
158 0           $kernel->select_read($socket);
159 0 0         $kernel->alarm_remove($heap->{jobs}->{$socket}->{timer}) if defined $heap->{jobs}->{$socket}->{timer};
160 0           delete $heap->{jobs}->{$socket}->{timer};
161 0           my $rsock = recv($socket, my $response = '', $heap->{bytes}, 0);
162              
163 0 0         if($response =~ /bad (?:rconpassword|rcon_password)/i) {
164 0           $kernel->post($heap->{jobs}->{$socket}->{session}, $heap->{jobs}->{$socket}->{postback},
165             $heap->{jobs}->{$socket}->{type},
166             $heap->{jobs}->{$socket}->{ip},
167             $heap->{jobs}->{$socket}->{port},
168             $heap->{jobs}->{$socket}->{cmd},
169             $heap->{jobs}->{$socket}->{identifier},
170             'ERROR: Bad Rcon password.');
171             } else {
172             # following regex's thanks to kkrcon
173 0           $response =~ s/\x00+$//; # terminator
174 0           $response =~ s/^\xff\xff\xff\xffl//; # new HL
175 0           $response =~ s/^\xff\xff\xff\xffn//; # qw
176 0           $response =~ s/^\xff\xff\xff\xff//; # q2/q3
177 0           $response =~ s/^\xfe\xff\xff\xff.....//; # old hl bug
178 0           $kernel->post($heap->{jobs}->{$socket}->{session}, $heap->{jobs}->{$socket}->{postback},
179             $heap->{jobs}->{$socket}->{type},
180             $heap->{jobs}->{$socket}->{ip},
181             $heap->{jobs}->{$socket}->{port},
182             $heap->{jobs}->{$socket}->{cmd},
183             $heap->{jobs}->{$socket}->{identifier},
184             $response);
185 0           print STDERR "Rcon Response was $response\n" if DEBUG;
186             }
187 0           delete($heap->{jobs}->{$socket});
188             }
189              
190             sub challenge_timeout {
191 0     0 0   my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
192 0 0         if($heap->{jobs}->{$socket}->{try} > ($heap->{retry} + 1)) {
193 0           $kernel->post($heap->{jobs}->{$socket}->{session}, $heap->{jobs}->{$socket}->{postback},
194             $heap->{jobs}->{$socket}->{type},
195             $heap->{jobs}->{$socket}->{ip},
196             $heap->{jobs}->{$socket}->{port},
197             $heap->{jobs}->{$socket}->{cmd},
198             $heap->{jobs}->{$socket}->{identifier},
199             'ERROR: Timed out trying to obtain challenge.');
200             } else {
201 0           print STDERR "Challenge request timed out for $socket. Retrying.\n" if DEBUG;
202 0           send($socket, "\xFF\xFF\xFF\xFFchallenge rcon\n\0", 0);
203 0           $heap->{jobs}->{$socket}->{timer} = $kernel->delay_set('challenge_timeout', $heap->{timeout}, $socket);
204 0           $heap->{jobs}->{$socket}->{try}++;
205             }
206             }
207              
208             sub rcon_timeout {
209 0     0 0   my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
210 0 0         if($heap->{jobs}->{$socket}->{try} > ($heap->{retry} + 1)) {
211 0           $kernel->post($heap->{jobs}->{$socket}->{session}, $heap->{jobs}->{$socket}->{postback},
212             $heap->{jobs}->{$socket}->{type},
213             $heap->{jobs}->{$socket}->{ip},
214             $heap->{jobs}->{$socket}->{port},
215             $heap->{jobs}->{$socket}->{cmd},
216             $heap->{jobs}->{$socket}->{identifier},
217             'ERROR: Timed out waiting for Rcon response.');
218             } else {
219 0           print STDERR "Rcon timed out for $socket. Retrying.\n" if DEBUG;
220 0 0         send($socket, "\xFF\xFF\xFF\xFFrcon " . $heap->{jobs}->{$socket}->{challenge} . " \"" . $heap->{jobs}->{$socket}->{pw} . "\" " . $heap->{jobs}->{$socket}->{cmd} . "\0", 0) if $heap->{jobs}->{$socket}->{type} =~ /hl$/;
221 0 0         send($socket, "\xFF\xFF\xFF\xFFrcon \"" . $heap->{jobs}->{$socket}->{pw} . "\" " . $heap->{jobs}->{$socket}->{cmd} . "\0", 0) if $heap->{jobs}->{$socket}->{type} =~ /^(?:q2|q3|qw)$/;
222 0           $heap->{jobs}->{$socket}->{timer} = $kernel->delay_set('rcon_timeout', $heap->{timeout}, $socket);
223 0           $heap->{jobs}->{$socket}->{try}++;
224             }
225             }
226              
227             sub players {
228 0     0 1   my ($kernel, $heap, $sender, $type, $ip, $port, $password, $postback) = @_[KERNEL, HEAP, SENDER, ARG0..ARG4];
229 0 0         my $identifier = defined($_[ARG5]) ? $_[ARG5] : undef;
230 0 0         croak "IP address required to execute an Rcon command" unless defined $ip;
231 0 0 0       croak "Port requred to execute an Rcon command" if !defined $port || $port !~ /^\d+$/;
232 0 0 0       croak "Password requires to execute an Rcon command" if !defined $password || $password eq '';
233 0 0         croak "Server type was not recognized" unless $type =~ /^(?:qw|q2|q3|oldhl|hl)$/;
234 0           my $jobid = $playerSN;
235 0           $playerSN++;
236 0           print STDERR "Got a request for players at $ip:$port with jobid $jobid\n" if DEBUG;
237 0           $kernel->yield('rcon', $type, $ip, $port, $password, 'status', 'player_response', $jobid);
238 0           $heap->{p_jobs}->{$jobid} = {
239             ip => $ip,
240             port => $port,
241             pw => $password,
242             identifier => $identifier,
243             session => $sender->ID(),
244             postback => $postback,
245             type => $type,
246             };
247             }
248              
249             sub player_response {
250 0     0 0   my ($kernel, $heap, $jobid, $response) = @_[KERNEL, HEAP, ARG4, ARG5];
251 0           print STDERR "Got a player request response for job $jobid\n" if DEBUG;
252 0 0         if($response =~ /^ERROR\: /) {
253 0           $kernel->post($heap->{p_jobs}->{$jobid}->{session},
254             $heap->{p_jobs}->{$jobid}->{postback},
255             $heap->{p_jobs}->{$jobid}->{type},
256             $heap->{p_jobs}->{$jobid}->{ip},
257             $heap->{p_jobs}->{$jobid}->{port},
258             $heap->{p_jobs}->{$jobid}->{identifier},
259             $response
260             ); # One of the errors generated from the rcon command...
261             } else {
262 0 0         if($heap->{p_jobs}->{$jobid}->{type} =~ /hl$/) {
    0          
263 0           $kernel->yield('player_parse_hl', $jobid, $response);
264             } elsif($heap->{p_jobs}->{$jobid}->{type} =~ /^(?:q2|q3|qw)$/) {
265 0           $kernel->yield('player_parse_quake', $jobid, $response);
266             }
267             }
268             }
269              
270             sub player_parse_hl {
271 0     0 0   my ($kernel, $heap, $jobid, $response) = @_[KERNEL, HEAP, ARG0, ARG1];
272             # This code is partially adapted from KKrcon
273 0           my %players;
274 0           foreach(split(/[\r\n]+/, $response)) {
275 0 0         if(/^\#[\s\d]\d\s+
276             (?:\")(.+)(?:\")\s+ # Player name
277             (\d+)\s+ # Player ID
278             (\d+)\s+ # WonID
279             ([\d-]+)\s+ # Frag count
280             ([\d:]+)\s+ # time
281             (\d+)\s+ # ping
282             (\d+)\s+ # packetloss
283             (\S+) # ip:port
284             $/x) {
285 0           $players{$2} = {
286             "Name" => $1,
287             "UserID" => $2,
288             "WonID" => $3,
289             "Frags" => $4,
290             "Time" => $5,
291             "Ping" => $6,
292             "Loss" => $7,
293             "Address" => $8,
294             };
295             }
296             }
297 0           $kernel->post($heap->{p_jobs}->{$jobid}->{session},
298             $heap->{p_jobs}->{$jobid}->{postback},
299             $heap->{p_jobs}->{$jobid}->{type},
300             $heap->{p_jobs}->{$jobid}->{ip},
301             $heap->{p_jobs}->{$jobid}->{port},
302             $heap->{p_jobs}->{$jobid}->{identifier},
303             \%players
304             );
305 0           delete($heap->{p_jobs}->{$jobid});
306             }
307              
308             sub player_parse_quake {
309 0     0 0   my ($kernel, $heap, $jobid, $response) = @_[KERNEL, HEAP, ARG0, ARG1];
310 0           my %players;
311 0           foreach(split(/[\r\n]+/, $response)) {
312 0 0         if(/^\s*
313             (\d+)\s+ # num
314             ([\d-]+)\s+ # score
315             (\d+)\s+ # ping
316             (.+) # name
317             \s+(\d+)\s+ # lastmsg
318             (\S+)\s+ # address
319             (\d+) # qport
320             (?:\s+(\d+)|) # rate
321             $/x) {
322 0           $players{$1} = {
323             "num" => $1,
324             "score" => $2,
325             "ping" => $3,
326             "name" => $4,
327             "lastmsg" => $5,
328             "address" => $6,
329             "qport" => $7,
330             };
331 0 0 0       if(defined($8) && $8 ne '') {
332 0           $players{$1}{"rate"} = $8;
333             }
334             }
335             }
336 0           $kernel->post($heap->{p_jobs}->{$jobid}->{session},
337             $heap->{p_jobs}->{$jobid}->{postback},
338             $heap->{p_jobs}->{$jobid}->{type},
339             $heap->{p_jobs}->{$jobid}->{ip},
340             $heap->{p_jobs}->{$jobid}->{port},
341             $heap->{p_jobs}->{$jobid}->{identifier},
342             \%players
343             );
344 0           delete($heap->{p_jobs}->{$jobid});
345             }
346             1;
347              
348             __END__