File Coverage

blib/lib/POE/Component/Server/NSCA.pm
Criterion Covered Total %
statement 287 334 85.9
branch 28 68 41.1
condition 4 10 40.0
subroutine 64 67 95.5
pod 6 6 100.0
total 389 485 80.2


line stmt bran cond sub pod time code
1             package POE::Component::Server::NSCA;
2             $POE::Component::Server::NSCA::VERSION = '0.10';
3             #ABSTRACT: a POE Component that implements NSCA daemon functionality
4              
5 3     3   40894 use strict;
  3         7  
  3         71  
6 3     3   12 use warnings;
  3         6  
  3         62  
7 3     3   1273 use Socket;
  3         9769  
  3         1087  
8 3     3   20 use Carp;
  3         6  
  3         111  
9 3     3   1071 use Net::Netmask;
  3         34155  
  3         240  
10 3     3   1269 use Math::Random;
  3         12408  
  3         212  
11 3     3   1193 use POE qw(Wheel::SocketFactory Wheel::ReadWrite Filter::Stream);
  3         64988  
  3         20  
12              
13 3     3   171490 use constant MAX_INPUT_BUFFER => 2048 ; # /* max size of most buffers we use */
  3         9  
  3         197  
14 3     3   18 use constant MAX_HOST_ADDRESS_LENGTH => 256 ; # /* max size of a host address */
  3         7  
  3         112  
15 3     3   33 use constant MAX_HOSTNAME_LENGTH => 64 ;
  3         7  
  3         99  
16 3     3   15 use constant MAX_DESCRIPTION_LENGTH => 128;
  3         6  
  3         93  
17 3     3   15 use constant OLD_PLUGINOUTPUT_LENGTH => 512;
  3         5  
  3         90  
18 3     3   14 use constant MAX_PLUGINOUTPUT_LENGTH => 4096;
  3         6  
  3         93  
19 3     3   14 use constant MAX_PASSWORD_LENGTH => 512;
  3         6  
  3         94  
20 3     3   13 use constant TRANSMITTED_IV_SIZE => 128;
  3         6  
  3         106  
21 3     3   14 use constant SIZEOF_U_INT32_T => 4;
  3         6  
  3         101  
22 3     3   14 use constant SIZEOF_INT16_T => 2;
  3         6  
  3         146  
23 3     3   22 use constant SIZEOF_INIT_PACKET => TRANSMITTED_IV_SIZE + SIZEOF_U_INT32_T;
  3         9  
  3         100  
24              
25 3     3   15 use constant PROBABLY_ALIGNMENT_ISSUE => 4;
  3         5  
  3         150  
26              
27 3     3   15 use constant SIZEOF_DATA_PACKET => SIZEOF_INT16_T + SIZEOF_U_INT32_T + SIZEOF_U_INT32_T + SIZEOF_INT16_T + MAX_HOSTNAME_LENGTH + MAX_DESCRIPTION_LENGTH + MAX_PLUGINOUTPUT_LENGTH + PROBABLY_ALIGNMENT_ISSUE;
  3         7  
  3         138  
28 3     3   15 use constant SIZEOF_OLD_PACKET => SIZEOF_INT16_T + SIZEOF_U_INT32_T + SIZEOF_U_INT32_T + SIZEOF_INT16_T + MAX_HOSTNAME_LENGTH + MAX_DESCRIPTION_LENGTH + OLD_PLUGINOUTPUT_LENGTH + PROBABLY_ALIGNMENT_ISSUE;
  3         5  
  3         96  
29              
30 3     3   15 use constant ENCRYPT_NONE => 0 ; # /* no encryption */
  3         6  
  3         98  
31 3     3   14 use constant ENCRYPT_XOR => 1 ; # /* not really encrypted, just obfuscated */
  3         5  
  3         94  
32 3     3   13 use constant ENCRYPT_DES => 2 ; # /* DES */
  3         5  
  3         106  
33 3     3   19 use constant ENCRYPT_3DES => 3 ; # /* 3DES or Triple DES */
  3         9  
  3         132  
34 3     3   19 use constant ENCRYPT_CAST128 => 4 ; # /* CAST-128 */
  3         28  
  3         105  
35 3     3   15 use constant ENCRYPT_CAST256 => 5 ; # /* CAST-256 */
  3         7  
  3         107  
36 3     3   14 use constant ENCRYPT_XTEA => 6 ; # /* xTEA */
  3         5  
  3         94  
37 3     3   13 use constant ENCRYPT_3WAY => 7 ; # /* 3-WAY */
  3         7  
  3         98  
38 3     3   16 use constant ENCRYPT_BLOWFISH => 8 ; # /* SKIPJACK */
  3         5  
  3         119  
39 3     3   17 use constant ENCRYPT_TWOFISH => 9 ; # /* TWOFISH */
  3         31  
  3         145  
40 3     3   14 use constant ENCRYPT_LOKI97 => 10 ; # /* LOKI97 */
  3         5  
  3         102  
41 3     3   13 use constant ENCRYPT_RC2 => 11 ; # /* RC2 */
  3         6  
  3         87  
42 3     3   21 use constant ENCRYPT_ARCFOUR => 12 ; # /* RC4 */
  3         9  
  3         102  
43 3     3   14 use constant ENCRYPT_RC6 => 13 ; # /* RC6 */ ; # /* UNUSED */
  3         6  
  3         95  
44 3     3   14 use constant ENCRYPT_RIJNDAEL128 => 14 ; # /* RIJNDAEL-128 */
  3         5  
  3         93  
45 3     3   13 use constant ENCRYPT_RIJNDAEL192 => 15 ; # /* RIJNDAEL-192 */
  3         7  
  3         99  
46 3     3   15 use constant ENCRYPT_RIJNDAEL256 => 16 ; # /* RIJNDAEL-256 */
  3         5  
  3         92  
47 3     3   13 use constant ENCRYPT_MARS => 17 ; # /* MARS */ ; # /* UNUSED */
  3         5  
  3         107  
48 3     3   14 use constant ENCRYPT_PANAMA => 18 ; # /* PANAMA */ ; # /* UNUSED */
  3         5  
  3         97  
49 3     3   13 use constant ENCRYPT_WAKE => 19 ; # /* WAKE */
  3         5  
  3         95  
50 3     3   65 use constant ENCRYPT_SERPENT => 20 ; # /* SERPENT */
  3         6  
  3         117  
51 3     3   13 use constant ENCRYPT_IDEA => 21 ; # /* IDEA */ ; # /* UNUSED */
  3         7  
  3         101  
52 3     3   18 use constant ENCRYPT_ENIGMA => 22 ; # /* ENIGMA (Unix crypt) */
  3         5  
  3         108  
53 3     3   14 use constant ENCRYPT_GOST => 23 ; # /* GOST */
  3         6  
  3         105  
54 3     3   14 use constant ENCRYPT_SAFER64 => 24 ; # /* SAFER-sk64 */
  3         6  
  3         111  
55 3     3   13 use constant ENCRYPT_SAFER128 => 25 ; # /* SAFER-sk128 */
  3         6  
  3         100  
56 3     3   14 use constant ENCRYPT_SAFERPLUS => 26 ; # /* SAFER+ */
  3         5  
  3         5911  
57              
58             my $HAVE_MCRYPT = 0;
59             eval {
60             require Mcrypt;
61             $HAVE_MCRYPT++;
62             };
63              
64             # Lookups for loading.
65             my %mcrypts = ( ENCRYPT_NONE, "none",
66             ENCRYPT_XOR, "xor",
67             ENCRYPT_DES, "DES",
68             ENCRYPT_3DES, "3DES",
69             ENCRYPT_CAST128, "CAST_128",
70             ENCRYPT_CAST256, "CAST-256",
71             ENCRYPT_XTEA, "XTEA",
72             ENCRYPT_3WAY, "THREEWAY",
73             ENCRYPT_BLOWFISH, "BLOWFISH",
74             ENCRYPT_TWOFISH, "TWOFISH",
75             ENCRYPT_LOKI97, "LOKI97",
76             ENCRYPT_RC2, "RC2",
77             ENCRYPT_ARCFOUR, "ARCFOUR",
78             ENCRYPT_RC6, "RC6",
79             ENCRYPT_RIJNDAEL128, "RIJNDAEL_128",
80             ENCRYPT_RIJNDAEL192, "RIJNDAEL_192",
81             ENCRYPT_RIJNDAEL256, "RIJNDAEL_256",
82             ENCRYPT_MARS, "MARS",
83             ENCRYPT_PANAMA, "PANAMA",
84             ENCRYPT_WAKE, "WAKE",
85             ENCRYPT_SERPENT, "SERPENT",
86             ENCRYPT_IDEA, "IDEA",
87             ENCRYPT_ENIGMA, "ENGIMA",
88             ENCRYPT_GOST, "GOST",
89             ENCRYPT_SAFER64, "SAFER_SK64",
90             ENCRYPT_SAFER128, "SAFER_SK128",
91             ENCRYPT_SAFERPLUS, "SAFERPLUS",
92             );
93              
94             sub spawn {
95 3     3 1 46 my $package = shift;
96 3         25 my %opts = @_;
97 3         33 $opts{lc $_} = delete $opts{$_} for keys %opts;
98             croak "$package requires a 'password' argument\n"
99 3 50       16 unless $opts{password};
100             croak "$package requires an 'encryption' argument\n"
101 3 50       14 unless defined $opts{encryption};
102             croak "'encryption' argument must be a valid numeric\n"
103 3 50       20 unless defined $mcrypts{ $opts{encryption} };
104 3         11 my $options = delete $opts{options};
105 3   50     37 my $access = delete $opts{access} || [ Net::Netmask->new('any') ];
106 3 50       333 $access = [ ] unless ref $access eq 'ARRAY';
107 3         12 foreach my $acl ( @$access ) {
108 3 50       32 next unless $acl->isa('Net::Netmask');
109 3         8 push @{ $opts{access} }, $acl;
  3         14  
110             }
111 3         11 my $self = bless \%opts, $package;
112 3 100       61 $self->{session_id} = POE::Session->create(
113             object_states => [
114             $self => { shutdown => '_shutdown',
115             },
116             $self => [qw(
117             _start
118             _accept_client
119             _accept_failed
120             _conn_input
121             _conn_error
122             _conn_alarm
123             register
124             unregister
125             )],
126             ],
127             heap => $self,
128             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
129             )->ID();
130 3         586 return $self;
131             }
132              
133             sub shutdown {
134 3     3 1 6939 my $self = shift;
135 3         19 $poe_kernel->post( $self->{session_id}, 'shutdown' );
136             }
137              
138             sub session_id {
139 2     2 1 56 return $_[0]->{session_id};
140             }
141              
142             sub getsockname {
143 3 50   3 1 2681 return unless $_[0]->{listener};
144 3         26 return $_[0]->{listener}->getsockname();
145             }
146              
147             sub _start {
148 3     3   1132 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
149 3         14 $self->{session_id} = $_[SESSION]->ID();
150 3 50       22 if ( $self->{alias} ) {
151 0         0 $kernel->alias_set( $self->{alias} );
152             }
153             else {
154 3         17 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
155             }
156             #$self->{filter} = POE::Filter::Block->new( BlockSize => SIZEOF_DATA_PACKET );
157 3         158 $self->{filter} = POE::Filter::Stream->new();
158             $self->{listener} = POE::Wheel::SocketFactory->new(
159             ( defined $self->{address} ? ( BindAddress => $self->{address} ) : () ),
160 3 50       73 ( defined $self->{port} ? ( BindPort => $self->{port} ) : ( BindPort => 5667 ) ),
    50          
161             SuccessEvent => '_accept_client',
162             FailureEvent => '_accept_failed',
163             SocketDomain => AF_INET, # Sets the socket() domain
164             SocketType => SOCK_STREAM, # Sets the socket() type
165             SocketProtocol => 'tcp', # Sets the socket() protocol
166             Reuse => 'on', # Lets the port be reused
167             );
168 3         1676 return;
169             }
170              
171             sub register {
172 2     2 1 168 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
173 2         8 my $sender_id = $sender->ID();
174 2         9 my %args;
175 2 50       9 if ( ref $_[ARG0] eq 'HASH' ) {
    0          
176 2         5 %args = %{ $_[ARG0] };
  2         11  
177             }
178             elsif ( ref $_[ARG0] eq 'ARRAY' ) {
179 0         0 %args = @{ $_[ARG0] };
  0         0  
180             }
181             else {
182 0         0 %args = @_[ARG0..$#_];
183             }
184 2         14 $args{lc $_} = delete $args{$_} for keys %args;
185 2 50       12 unless ( $args{event} ) {
186 0         0 warn "No 'event' argument supplied\n";
187 0         0 return;
188             }
189 2 50       13 if ( defined $self->{sessions}->{ $sender_id } ) {
190 0         0 $self->{sessions}->{ $sender_id } = \%args;
191             }
192             else {
193 2         10 $self->{sessions}->{ $sender_id } = \%args;
194 2         44 $kernel->refcount_increment( $sender_id, __PACKAGE__ );
195             }
196 2         67 return;
197             }
198              
199             sub unregister {
200 0     0 1 0 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
201 0         0 my $sender_id = $sender->ID();
202 0         0 my %args;
203 0 0       0 if ( ref $_[ARG0] eq 'HASH' ) {
    0          
204 0         0 %args = %{ $_[ARG0] };
  0         0  
205             }
206             elsif ( ref $_[ARG0] eq 'ARRAY' ) {
207 0         0 %args = @{ $_[ARG0] };
  0         0  
208             }
209             else {
210 0         0 %args = @_[ARG0..$#_];
211             }
212 0         0 $args{lc $_} = delete $args{$_} for keys %args;
213 0         0 my $data = delete $self->{sessions}->{ $sender_id };
214 0 0       0 $kernel->refcount_decrement( $sender_id, __PACKAGE__ ) if $data;
215 0         0 return;
216             }
217              
218             sub _shutdown {
219 3     3   933 my ($kernel,$self) = @_[KERNEL,OBJECT];
220 3         23 delete $self->{listener};
221 3         676 delete $self->{clients};
222 3         8 $kernel->refcount_decrement( $_, __PACKAGE__ ) for keys %{ $self->{sessions} };
  3         19  
223 3         121 $kernel->alarm_remove_all();
224 3         134 $kernel->alias_remove( $_ ) for $kernel->alias_list();
225 3 50       116 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
226 3         145 return;
227             }
228              
229             sub _accept_failed {
230 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
231 0         0 warn "Listener: $wheel_id generated $operation error $errnum: $errstr\n";
232 0         0 delete $self->{listener};
233 0         0 $kernel->yield( '_shutdown' );
234 0         0 return;
235             }
236              
237             sub _accept_client {
238 3     3   4787 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
239 3         52 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( CORE::getsockname $socket ) )[1] );
240 3         17 my $sockport = ( unpack_sockaddr_in ( CORE::getsockname $socket ) )[0];
241 3         15 $peeraddr = inet_ntoa( $peeraddr );
242              
243 3 50       6 return unless grep { $_->match( $peeraddr ) } @{ $self->{access} };
  3         19  
  3         14  
244              
245             my $wheel = POE::Wheel::ReadWrite->new(
246             Handle => $socket,
247             Filter => $self->{filter},
248 3         262 InputEvent => '_conn_input',
249             ErrorEvent => '_conn_error',
250             FlushedEvent => '_conn_flushed',
251             );
252              
253 3 50       1123 return unless $wheel;
254              
255 3         15 my $id = $wheel->ID();
256 3         24 my $time = time();
257 3         37 my $iv = join '', random_uniform_integer(128,0,9);
258 3         698 my $init_packet = $iv . pack 'N', $time;
259 3         26 $self->{clients}->{ $id } =
260             {
261             wheel => $wheel,
262             peeraddr => $peeraddr,
263             peerport => $peerport,
264             sockaddr => $sockaddr,
265             sockport => $sockport,
266             ts => $time,
267             iv => $iv,
268             };
269 3   50     24 $self->{clients}->{ $id }->{alarm} = $kernel->delay_set( '_conn_alarm', $self->{time_out} || 60, $id );
270 3         244 $wheel->put( $init_packet );
271 3         266 return;
272             }
273              
274             sub _conn_exists {
275 5     5   15 my ($self,$wheel_id) = @_;
276 5 50 33     47 return 0 unless $wheel_id and defined $self->{clients}->{ $wheel_id };
277 5         22 return 1;
278             }
279              
280             sub _conn_error {
281 2     2   1275 my ($self,$errstr,$id) = @_[OBJECT,ARG2,ARG3];
282 2 50       10 return unless $self->_conn_exists( $id );
283 2         13 delete $self->{clients}->{ $id };
284 2         564 return;
285             }
286              
287             sub _conn_alarm {
288 1     1   10010103 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
289 1 50       7 return unless $self->_conn_exists( $id );
290 1         15 delete $self->{clients}->{ $id };
291 1         518 return;
292             }
293              
294             sub _conn_input {
295 2     2   26586 my ($kernel,$self,$packet,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
296 2 50       10 return unless $self->_conn_exists( $id );
297 2         6 my $client = $self->{clients}->{ $id };
298 2         23 $kernel->alarm_remove( delete $client->{alarm} );
299 2         203 my $data_packet_length = SIZEOF_DATA_PACKET;
300 2 50       17 if ( length( $packet ) == SIZEOF_OLD_PACKET ) {
301 2         6 $data_packet_length = SIZEOF_OLD_PACKET;
302             }
303 2         10 my $input = _decrypt( $packet, $self->{encryption}, $client->{iv}, $self->{password}, $data_packet_length );
304 2 50       9 return unless $input; # something wrong with the decryption
305 2         13 my $version = unpack 'n', substr $input, 0, 4;
306 2 0 33     13 return unless $version == 3 or $client->{version_already_checked}; # Wrong version received
307 2         7 $client->{version_already_checked} = 1;
308 2         7 my $crc32 = unpack 'N', substr $input, 4, 4;
309 2         5 my $ts = unpack 'N', substr $input, 8, 4;
310 2         6 my $rc = unpack 'n', substr $input, 12, 2;
311 2         6 my $firstbit = substr $input, 0, 4;
312 2         5 my $secondbit = substr $input, 8;
313 2         9 my $checksum = _calculate_crc32( $firstbit . pack('N', 0) . $secondbit );
314 2         23 my @data = unpack 'a[64]a[128]a[512]', substr $input, 14;
315 2         38 s/\000.*$// for @data;
316 2         18 my $result = {
317             version => $version,
318             crc32 => $crc32,
319             checksum => $checksum,
320             return_code => $rc,
321             timestamp => $ts,
322             };
323 2         20 $result->{$_} = shift @data for qw(host_name svc_description plugin_output);
324 2         19 $result->{$_} = $client->{$_} for qw(peeraddr peerport sockaddr sockport ts iv);
325             $kernel->post( $_, $self->{sessions}->{$_}->{event}, $result, $self->{sessions}->{$_}->{context} )
326 2         13 for keys %{ $self->{sessions} };
  2         51  
327 2         311 return;
328             }
329              
330             #/* calculates the CRC 32 value for a buffer */
331             sub _calculate_crc32 {
332 2     2   6 my $string = shift;
333              
334 2         6 my $crc32_table = _generate_crc32_table();
335 2         6 my $crc = 0xFFFFFFFF;
336              
337 2         130 foreach my $tchar (split(//, $string)) {
338 1440         1982 my $char = ord($tchar);
339 1440         2377 $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc32_table->[($crc ^ $char) & 0xFF];
340             }
341              
342 2         63 return ($crc ^ 0xFFFFFFFF);
343             }
344              
345             #/* build the crc table - must be called before calculating the crc value */
346             sub _generate_crc32_table {
347 2     2   5 my $crc32_table = [];
348 2         5 my $poly = 0xEDB88320;
349              
350 2         11 for (my $i = 0; $i < 256; $i++){
351 512         707 my $crc = $i;
352 512         997 for (my $j = 8; $j > 0; $j--) {
353 4096 100       6748 if ($crc & 1) {
354 2048         4189 $crc = ($crc >> 1) ^ $poly;
355             }
356             else {
357 2048         4032 $crc = ($crc >> 1);
358             }
359             }
360 512         1067 $crc32_table->[$i] = $crc;
361             }
362 2         9 return $crc32_table;
363             }
364              
365             # central switchboard for encryption methods.
366             sub _decrypt {
367 2     2   9 my ($data_packet_string, $encryption_method, $iv_salt, $password, $data_packet_length) = @_;
368              
369 2         5 my $crypted;
370 2 100       12 if ($encryption_method == ENCRYPT_NONE) {
    50          
371 1         2 $crypted = $data_packet_string;
372             }
373             elsif ($encryption_method == ENCRYPT_XOR) {
374 1         4 $crypted = _decrypt_xor($data_packet_string, $iv_salt, $password, $data_packet_length);
375             }
376             else {
377 0         0 $crypted = _decrypt_mcrypt( $data_packet_string, $encryption_method, $iv_salt, $password );
378             }
379 2         8 return $crypted;
380             }
381              
382             sub _decrypt_xor {
383 1     1   4 my ($data_packet_string, $iv_salt, $password, $data_packet_length) = @_;
384              
385 1         66 my @out = split(//, $data_packet_string);
386 1         19 my @salt_iv = split(//, $iv_salt);
387 1         4 my @salt_pw = split(//, $password);
388              
389 1         2 my $y = 0;
390 1         3 my $x = 0;
391              
392             #/* rotate over IV we received from the server... */
393 1         4 while ($y < $data_packet_length) {
394             #/* keep rotating over IV */
395 720         1133 $out[$y] = $out[$y] ^ $salt_iv[$x % scalar(@salt_iv)];
396              
397 720         963 $y++;
398 720         1343 $x++;
399             }
400              
401             #/* rotate over password... */
402 1         3 $y=0;
403 1         3 $x=0;
404 1         5 while ($y < $data_packet_length){
405             #/* keep rotating over password */
406 720         1108 $out[$y] = $out[$y] ^ $salt_pw[$x % scalar(@salt_pw)];
407              
408 720         976 $y++;
409 720         1342 $x++;
410             }
411 1         57 return join '', @out;
412             }
413              
414             sub _decrypt_mcrypt {
415 0     0     my ( $data_packet_string, $encryption_method, $iv_salt, $password ) = @_;
416 0           my $crypted;
417 0           my $evalok = 0;
418 0 0         if( $HAVE_MCRYPT ){
419             # Initialise the routine
420 0 0         if( defined( $mcrypts{$encryption_method} ) ){
421             # Load the routine.
422 0           my $routine = $mcrypts{$encryption_method};
423 0           eval {
424             # This sometimes dies with 'mcrypt is not of type MCRYPT'.
425 0           my $td = Mcrypt->new( algorithm => &{\&{"Mcrypt::".$routine}}(), mode => Mcrypt::CFB(), verbose => 0 );
  0            
  0            
426 0           my $key = $password;
427 0           my $iv = substr $iv_salt, 0, $td->{IV_SIZE};
428 0 0         if( defined( $td ) ){
429 0           $td->init($key, $iv);
430 0           for (my $i = 0; $i < length( $data_packet_string ); $i++ ) {
431 0           $crypted .= $td->decrypt( substr $data_packet_string, 0+$i, 1 );
432             }
433 0           $td->end();
434             }
435 0           $evalok++;
436             };
437 0 0         warn "$@\n" if $@;
438             }
439             } else {
440 0           warn "Mcrypt module missing\n";
441             }
442 0           return $crypted;
443             }
444              
445             'Hon beiriant goes at hun ar ddeg';
446              
447             __END__