File Coverage

blib/lib/POE/Component/Client/NSCA.pm
Criterion Covered Total %
statement 302 358 84.3
branch 28 66 42.4
condition 5 22 22.7
subroutine 73 76 96.0
pod 1 1 100.0
total 409 523 78.2


line stmt bran cond sub pod time code
1             package POE::Component::Client::NSCA;
2             $POE::Component::Client::NSCA::VERSION = '0.18';
3             #ABSTRACT: a POE Component that implements send_nsca functionality
4              
5 4     4   53390 use strict;
  4         8  
  4         93  
6 4     4   18 use warnings;
  4         8  
  4         92  
7 4     4   1497 use POE qw(Wheel::SocketFactory Filter::Stream Wheel::ReadWrite);
  4         122797  
  4         24  
8 4     4   228432 use Carp;
  4         9  
  4         212  
9 4     4   22 use Socket;
  4         10  
  4         1567  
10 4     4   1704 use integer;
  4         51  
  4         16  
11              
12 4     4   120 use constant PROGRAM_VERSION => "1.2.0b4-Perl";
  4         9  
  4         222  
13 4     4   21 use constant MODIFICATION_DATE => "16-03-2006";
  4         8  
  4         178  
14              
15 4     4   21 use constant OK => 0;
  4         9  
  4         162  
16 4     4   31 use constant ERROR => -1;
  4         8  
  4         132  
17              
18 4     4   18 use constant TRUE => 1;
  4         7  
  4         115  
19 4     4   19 use constant FALSE => 0;
  4         8  
  4         115  
20              
21 4     4   18 use constant STATE_CRITICAL => 2 ; # /* service state return codes */
  4         7  
  4         124  
22 4     4   19 use constant STATE_WARNING => 1 ;
  4         7  
  4         131  
23 4     4   19 use constant STATE_OK => 0 ;
  4         7  
  4         136  
24 4     4   18 use constant STATE_UNKNOWN => 3 ; # Updated for Nagios.
  4         8  
  4         158  
25              
26 4     4   23 use constant DEFAULT_SOCKET_TIMEOUT => 10 ; # /* timeout after 10 seconds */
  4         11  
  4         131  
27 4     4   19 use constant DEFAULT_SERVER_PORT => 5667 ; # /* default port to use */
  4         6  
  4         143  
28              
29 4     4   25 use constant MAX_INPUT_BUFFER => 2048 ; # /* max size of most buffers we use */
  4         7  
  4         127  
30 4     4   18 use constant MAX_HOST_ADDRESS_LENGTH => 256 ; # /* max size of a host address */
  4         5  
  4         139  
31 4     4   17 use constant MAX_HOSTNAME_LENGTH => 64 ;
  4         7  
  4         127  
32 4     4   17 use constant MAX_DESCRIPTION_LENGTH => 128;
  4         7  
  4         128  
33 4     4   21 use constant MAX_PLUGINOUTPUT_LENGTH => 512;
  4         6  
  4         210  
34 4     4   19 use constant MAX_PASSWORD_LENGTH => 512;
  4         8  
  4         197  
35              
36 4     4   19 use constant ENCRYPT_NONE => 0 ; # /* no encryption */
  4         8  
  4         128  
37 4     4   18 use constant ENCRYPT_XOR => 1 ; # /* not really encrypted, just obfuscated */
  4         8  
  4         125  
38 4     4   17 use constant ENCRYPT_DES => 2 ; # /* DES */
  4         31  
  4         132  
39 4     4   18 use constant ENCRYPT_3DES => 3 ; # /* 3DES or Triple DES */
  4         8  
  4         143  
40 4     4   19 use constant ENCRYPT_CAST128 => 4 ; # /* CAST-128 */
  4         7  
  4         130  
41 4     4   19 use constant ENCRYPT_CAST256 => 5 ; # /* CAST-256 */
  4         7  
  4         128  
42 4     4   19 use constant ENCRYPT_XTEA => 6 ; # /* xTEA */
  4         27  
  4         151  
43 4     4   20 use constant ENCRYPT_3WAY => 7 ; # /* 3-WAY */
  4         6  
  4         144  
44 4     4   20 use constant ENCRYPT_BLOWFISH => 8 ; # /* SKIPJACK */
  4         8  
  4         128  
45 4     4   18 use constant ENCRYPT_TWOFISH => 9 ; # /* TWOFISH */
  4         7  
  4         169  
46 4     4   19 use constant ENCRYPT_LOKI97 => 10 ; # /* LOKI97 */
  4         7  
  4         147  
47 4     4   18 use constant ENCRYPT_RC2 => 11 ; # /* RC2 */
  4         11  
  4         180  
48 4     4   20 use constant ENCRYPT_ARCFOUR => 12 ; # /* RC4 */
  4         7  
  4         141  
49 4     4   19 use constant ENCRYPT_RC6 => 13 ; # /* RC6 */ ; # /* UNUSED */
  4         8  
  4         122  
50 4     4   19 use constant ENCRYPT_RIJNDAEL128 => 14 ; # /* RIJNDAEL-128 */
  4         7  
  4         122  
51 4     4   17 use constant ENCRYPT_RIJNDAEL192 => 15 ; # /* RIJNDAEL-192 */
  4         8  
  4         131  
52 4     4   17 use constant ENCRYPT_RIJNDAEL256 => 16 ; # /* RIJNDAEL-256 */
  4         8  
  4         129  
53 4     4   16 use constant ENCRYPT_MARS => 17 ; # /* MARS */ ; # /* UNUSED */
  4         7  
  4         122  
54 4     4   19 use constant ENCRYPT_PANAMA => 18 ; # /* PANAMA */ ; # /* UNUSED */
  4         6  
  4         126  
55 4     4   18 use constant ENCRYPT_WAKE => 19 ; # /* WAKE */
  4         7  
  4         135  
56 4     4   19 use constant ENCRYPT_SERPENT => 20 ; # /* SERPENT */
  4         7  
  4         120  
57 4     4   18 use constant ENCRYPT_IDEA => 21 ; # /* IDEA */ ; # /* UNUSED */
  4         9  
  4         137  
58 4     4   19 use constant ENCRYPT_ENIGMA => 22 ; # /* ENIGMA (Unix crypt) */
  4         8  
  4         133  
59 4     4   19 use constant ENCRYPT_GOST => 23 ; # /* GOST */
  4         8  
  4         122  
60 4     4   17 use constant ENCRYPT_SAFER64 => 24 ; # /* SAFER-sk64 */
  4         6  
  4         121  
61 4     4   19 use constant ENCRYPT_SAFER128 => 25 ; # /* SAFER-sk128 */
  4         5  
  4         122  
62 4     4   17 use constant ENCRYPT_SAFERPLUS => 26 ; # /* SAFER+ */
  4         8  
  4         126  
63              
64 4     4   18 use constant TRANSMITTED_IV_SIZE => 128 ; # /* size of IV to transmit - must be as big as largest IV needed for any crypto algorithm */
  4         6  
  4         139  
65              
66              
67 4     4   18 use constant NSCA_PACKET_VERSION_3 => 3 ; # /* packet version identifier */
  4         7  
  4         132  
68 4     4   18 use constant NSCA_PACKET_VERSION_2 => 2 ; # /* packet version identifier */
  4         8  
  4         134  
69 4     4   21 use constant NSCA_PACKET_VERSION_1 => 1 ; # /* older packet version identifier */
  4         8  
  4         124  
70              
71 4     4   18 use constant SIZEOF_U_INT32_T => 4;
  4         8  
  4         158  
72 4     4   24 use constant SIZEOF_INT16_T => 2;
  4         8  
  4         189  
73 4     4   21 use constant SIZEOF_INIT_PACKET => TRANSMITTED_IV_SIZE + SIZEOF_U_INT32_T;
  4         8  
  4         149  
74              
75 4     4   20 use constant PROBABLY_ALIGNMENT_ISSUE => 4;
  4         7  
  4         185  
76              
77 4     4   19 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;
  4         7  
  4         7068  
78              
79             # Work out whether we have the mcrypt libraries on board.
80             my $HAVE_MCRYPT = 0;
81             eval {
82             require Mcrypt;
83             $HAVE_MCRYPT++;
84             };
85              
86             # Lookups for loading.
87             my %mcrypts = ( ENCRYPT_DES, "des",
88             ENCRYPT_3DES, "3des",
89             ENCRYPT_CAST128, "cast-128",
90             ENCRYPT_CAST256, "cast-256",
91             ENCRYPT_XTEA, "xtea",
92             ENCRYPT_3WAY, "threeway",
93             ENCRYPT_BLOWFISH, "blowfish",
94             ENCRYPT_TWOFISH, "twofish",
95             ENCRYPT_LOKI97, "loki97",
96             ENCRYPT_RC2, "rc2",
97             ENCRYPT_ARCFOUR, "arcfour",
98             ENCRYPT_RC6, "rc6",
99             ENCRYPT_RIJNDAEL128, "rijndael-128",
100             ENCRYPT_RIJNDAEL192, "rijndael-192",
101             ENCRYPT_RIJNDAEL256, "rijndael-256",
102             ENCRYPT_MARS, "mars",
103             ENCRYPT_PANAMA, "panama",
104             ENCRYPT_WAKE, "wake",
105             ENCRYPT_SERPENT, "serpent",
106             ENCRYPT_IDEA, "idea",
107             ENCRYPT_ENIGMA, "engima",
108             ENCRYPT_GOST, "gost",
109             ENCRYPT_SAFER64, "safer-sk64",
110             ENCRYPT_SAFER128, "safer-sk128",
111             ENCRYPT_SAFERPLUS, "saferplus",
112             );
113              
114             sub send_nsca {
115 4     4 1 3394 my $package = shift;
116 4         30 my %params = @_;
117 4         42 $params{lc $_} = delete $params{$_} for keys %params;
118             croak "$package requires a 'host' argument\n"
119 4 50       20 unless $params{host};
120             croak "$package requires an 'event' argument\n"
121 4 50       14 unless $params{event};
122             croak "$package requires a 'password' argument\n"
123 4 50 33     29 unless $params{password} || $params{encryption} eq ENCRYPT_XOR;
124             croak "$package requires an 'encryption' argument\n"
125 4 50       16 unless defined $params{encryption};
126             croak "$package requires a 'message' argument and it must be a hashref\n"
127 4 50 33     36 unless $params{message} and ref $params{message} eq 'HASH';
128 4         12 foreach my $item ( qw(host_name return_code plugin_output) ) {
129             croak "'message' hashref must have a '$item' key\n"
130 12 50       39 unless defined $params{message}->{$item};
131             }
132 4         19 _correct_message( $params{message} );
133 4 50       13 $params{port} = 5667 unless defined $params{port};
134 4 50 33     22 $params{timeout} = 10 unless defined $params{timeout} and $params{timeout} =~ /^\d+$/;
135 4         9 my $options = delete $params{options};
136 4         12 my $self = bless \%params, $package;
137 4 50       72 $self->{session_id} = POE::Session->create(
138             object_states => [
139             $self => [ qw(_start _connect _sock_up _sock_err _sock_in _sock_flush _sock_down _send_response _timeout) ],
140             ],
141             heap => $self,
142             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
143             )->ID();
144 4         574 return $self;
145             }
146              
147             sub _start {
148 4     4   1135 my ($kernel,$sender,$self) = @_[KERNEL,SENDER,OBJECT];
149 4         17 $self->{session_id} = $_[SESSION]->ID();
150 4         45 $self->{filter} = POE::Filter::Stream->new();
151 4 50 33     58 if ( $kernel == $sender and !$self->{session} ) {
152 0         0 croak "Not called from another POE session and 'session' wasn't set\n";
153             }
154 4         9 my $sender_id;
155 4 50       14 if ( $self->{session} ) {
156 0 0       0 if ( my $ref = $kernel->alias_resolve( $self->{session} ) ) {
157 0         0 $sender_id = $ref->ID();
158             }
159             else {
160 0         0 croak "Could not resolve 'session' to a valid POE session\n";
161             }
162             }
163             else {
164 4         16 $sender_id = $sender->ID();
165             }
166 4         29 $kernel->refcount_increment( $sender_id, __PACKAGE__ );
167 4         173 $self->{sender_id} = $sender_id;
168 4         20 $kernel->yield( '_connect' );
169 4         389 return;
170             }
171              
172             sub _connect {
173 4     4   3565 my ($kernel,$self) = @_[KERNEL,OBJECT];
174             $self->{sockfactory} = POE::Wheel::SocketFactory->new(
175             SocketProtocol => 'tcp',
176             RemoteAddress => $self->{host},
177             RemotePort => $self->{port},
178 4         33 SuccessEvent => '_sock_up',
179             FailureEvent => '_sock_err',
180             );
181 4         25678 $kernel->delay( '_timeout', $self->{timeout} );
182 4         450 return;
183             }
184              
185             sub _timeout {
186 0     0   0 my ($kernel,$self) = @_[KERNEL,OBJECT];
187 0         0 delete $self->{sockfactory};
188 0         0 delete $self->{socket};
189 0         0 $kernel->yield( '_send_response', 'timeout' );
190 0         0 return;
191             }
192              
193             sub _sock_err {
194 2     2   760 my ($kernel,$self) = @_[KERNEL,OBJECT];
195 2         8 $kernel->delay( '_timeout' );
196 2         211 delete $self->{sockfactory};
197 2         70 $kernel->yield( '_send_response', 'sockerr', @_[ARG0..ARG2] );
198 2         161 return;
199             }
200              
201             sub _sock_up {
202 2     2   3276 my ($kernel,$self,$socket) = @_[KERNEL,OBJECT,ARG0];
203 2         9 $kernel->delay( '_timeout' );
204 2         210 delete $self->{sockfactory};
205             $self->{socket} = new POE::Wheel::ReadWrite
206             ( Handle => $socket,
207             Filter => $self->{filter},
208 2         51 InputEvent => '_sock_in',
209             ErrorEvent => '_sock_down',
210             FlushedEvent => '_sock_flush',
211             );
212 2         564 $self->{state} = 'init';
213 2         9 $kernel->delay( '_timeout', $self->{timeout} );
214 2         182 return;
215             }
216              
217             sub _sock_down {
218 0     0   0 my ($kernel,$self) = @_[KERNEL,OBJECT];
219 0         0 delete $self->{socket};
220 0         0 $kernel->delay( '_timeout' );
221 0         0 return;
222             }
223              
224             sub _sock_in {
225 2     2   1270 my ($kernel,$self,$input) = @_[KERNEL,OBJECT,ARG0];
226 2 50       10 unless ( length( $input ) == SIZEOF_INIT_PACKET ) {
227 0         0 $kernel->yield( '_send_response', 'badinit', length( $input ) );
228 0         0 delete $self->{socket};
229 0         0 return;
230             }
231 2         12 my $init_packet = {
232             iv => substr($input, 0, TRANSMITTED_IV_SIZE),
233             timestamp => substr($input, TRANSMITTED_IV_SIZE, SIZEOF_U_INT32_T),
234             };
235 2         4 my $data_packet_string_a = pack('n', NSCA_PACKET_VERSION_3) . "\000\000";
236             my $data_packet_string_b =
237             $init_packet->{timestamp} .
238             pack('n', $self->{message}->{return_code}) .
239             pack(('a'.MAX_HOSTNAME_LENGTH), $self->{message}->{host_name}) .
240             pack(('a'.MAX_DESCRIPTION_LENGTH), $self->{message}->{svc_description} || '') .
241 2   50     47 pack(('a'.MAX_PLUGINOUTPUT_LENGTH), $self->{message}->{plugin_output}) .
242             "\000\000";
243 2         15 my $crc = _calculate_crc32( $data_packet_string_a . pack( 'N', 0 ) . $data_packet_string_b);
244 2         20 my $data_packet_string = $data_packet_string_a . pack('N', $crc) . $data_packet_string_b;
245 2         16 my $data_packet_string_crypt = _encrypt($data_packet_string, $self->{encryption}, $init_packet->{iv}, $self->{password} );
246 2 50       9 unless ( $data_packet_string_crypt ) {
247 0         0 $kernel->yield( '_send_response', 'badencrypt' );
248 0         0 delete $self->{socket};
249 0         0 return;
250             }
251 2         20 $self->{socket}->put( $data_packet_string_crypt );
252 2         200 return;
253             }
254              
255             sub _sock_flush {
256 2     2   864 my ($kernel,$self) = @_[KERNEL,OBJECT];
257 2         9 $kernel->delay( '_timeout' );
258 2         218 delete $self->{socket};
259 2         530 $kernel->yield( '_send_response', 'success' );
260 2         152 return;
261             }
262              
263             sub _send_response {
264 4     4   800 my ($kernel,$self,$type) = @_[KERNEL,OBJECT,ARG0];
265 4         12 my $response = { };
266 4         27 $response->{$_} = $self->{$_} for qw(host message context);
267             SWITCH: {
268 4 50       10 if ( $type eq 'badinit' ) {
  4         18  
269 0         0 $response->{error} = 'Error: bad initialisation string from peer expected' . SIZEOF_INIT_PACKET . ' got ' . $_[ARG1];
270 0         0 last SWITCH;
271             }
272 4 50       22 if ( $type eq 'badencrypt' ) {
273 0         0 $response->{error} = 'Error: There was a problem with the encryption';
274 0         0 last SWITCH;
275             }
276 4 100       21 if ( $type eq 'sockerr' ) {
277 2         14 $response->{error} = 'Error: socket error: ' . join(' ', @_[ARG1..$#_]);
278 2         6 last SWITCH;
279             }
280 2 50       8 if ( $type eq 'timeout' ) {
281 0         0 $response->{error} = sprintf("Error: Socket timeout after %d seconds.", $self->{timeout} );
282 0         0 last SWITCH;
283             }
284 2         6 $response->{success} = 1;
285             }
286 4         24 $kernel->post( $self->{sender_id}, $self->{event}, $response );
287 4         491 $kernel->refcount_decrement( $self->{sender_id}, __PACKAGE__ );
288 4         207 $kernel->alarm_remove_all();
289 4         233 return;
290             }
291              
292             # truncates long fields
293             sub _correct_message {
294 4     4   10 my $message = shift;
295              
296 4 50       23 $message->{'svc_description'} = '' unless defined $message->{'svc_description'};
297 4 50       18 if (length( $message->{'host_name'} ) >= MAX_HOSTNAME_LENGTH) {
298 0         0 warn("Hostname too long - truncated");
299 0         0 $message->{'host_name'} = substr($message->{'host_name'}, 0, MAX_HOSTNAME_LENGTH-1);
300             }
301 4 50       16 if (length( $message->{'svc_description'} ) >= MAX_DESCRIPTION_LENGTH) {
302 0         0 warn("Description too long - truncated");
303 0         0 $message->{'svc_description'} = substr($message->{'svc_description'}, 0, MAX_DESCRIPTION_LENGTH-1);
304             }
305 4 50       17 if (length( $message->{'plugin_output'} ) >= MAX_PLUGINOUTPUT_LENGTH) {
306 0         0 warn("Plugin Output too long - truncated");
307 0         0 $message->{'plugin_output'} = substr($message->{'plugin_output'}, 0, MAX_PLUGINOUTPUT_LENGTH-1);
308             }
309 4         9 return $message;
310             }
311              
312              
313             #/* calculates the CRC 32 value for a buffer */
314             sub _calculate_crc32 {
315 2     2   4 my $string = shift;
316              
317 2         8 my $crc32_table = _generate_crc32_table();
318 2         6 my $crc = 0xFFFFFFFF;
319              
320 2         248 foreach my $tchar (split(//, $string)) {
321 1440         2078 my $char = ord($tchar);
322 1440         2430 $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc32_table->[($crc ^ $char) & 0xFF];
323             }
324              
325 2         68 return ($crc ^ 0xFFFFFFFF);
326             }
327              
328             #/* build the crc table - must be called before calculating the crc value */
329             sub _generate_crc32_table {
330 2     2   5 my $crc32_table = [];
331 2         5 my $poly = 0xEDB88320;
332              
333 2         9 for (my $i = 0; $i < 256; $i++){
334 512         735 my $crc = $i;
335 512         1035 for (my $j = 8; $j > 0; $j--) {
336 4096 100       6970 if ($crc & 1) {
337 2048         4530 $crc = ($crc >> 1) ^ $poly;
338             } else {
339 2048         4112 $crc = ($crc >> 1);
340             }
341             }
342 512         1160 $crc32_table->[$i] = $crc;
343             }
344 2         11 return $crc32_table;
345             }
346              
347             # central switchboard for encryption methods.
348             sub _encrypt {
349 2     2   8 my ($data_packet_string, $encryption_method, $iv_salt, $password) = @_;
350              
351 2         4 my $crypted;
352 2 100       19 if ($encryption_method == ENCRYPT_NONE) {
    50          
353 1         4 $crypted = $data_packet_string;
354             } elsif ($encryption_method == ENCRYPT_XOR) {
355 1         4 $crypted = _encrypt_xor($data_packet_string, $iv_salt, $password);
356             } else {
357 0         0 $crypted = _encrypt_mcrypt( $data_packet_string, $encryption_method, $iv_salt, $password );
358             }
359 2         7 return $crypted;
360             }
361              
362             sub _encrypt_xor {
363 1     1   3 my ($data_packet_string, $iv_salt, $password) = @_;
364              
365 1         68 my @out = split(//, $data_packet_string);
366 1         18 my @salt_iv = split(//, $iv_salt);
367 1         3 my @salt_pw = split(//, $password);
368              
369 1         2 my $y = 0;
370 1         3 my $x = 0;
371              
372             #/* rotate over IV we received from the server... */
373 1         5 while ($y < SIZEOF_DATA_PACKET) {
374             #/* keep rotating over IV */
375 720         1105 $out[$y] = $out[$y] ^ $salt_iv[$x % scalar(@salt_iv)];
376              
377 720         973 $y++;
378 720         1369 $x++;
379             }
380              
381 1 50       5 if ($password) {
382             #/* rotate over password... */
383 1         2 $y=0;
384 1         2 $x=0;
385 1         4 while ($y < SIZEOF_DATA_PACKET){
386             #/* keep rotating over password */
387 720         1092 $out[$y] = $out[$y] ^ $salt_pw[$x % scalar(@salt_pw)];
388              
389 720         963 $y++;
390 720         1373 $x++;
391             }
392             }
393 1         48 return( join('',@out) );
394             }
395              
396             sub _encrypt_mcrypt {
397 0     0     my ( $data_packet_string, $encryption_method, $iv_salt, $password ) = @_;
398 0           my $crypted;
399 0           my $evalok = 0;
400 0 0         if( $HAVE_MCRYPT ){
401             # Initialise the routine
402 0 0         if( defined( $mcrypts{$encryption_method} ) ){
403             # Load the routine.
404 0           my $routine = $mcrypts{$encryption_method};
405 0           eval {
406             # This sometimes dies with 'mcrypt is not of type MCRYPT'.
407 0           my $td = Mcrypt->new( algorithm => $routine, mode => 'cfb', verbose => 0 );
408 0           my $key = $password;
409 0           my $iv = substr $iv_salt, 0, $td->{IV_SIZE};
410 0 0         if( defined( $td ) ){
411 0           $td->init($key, $iv);
412 0           for (my $i = 0; $i < length( $data_packet_string ); $i++ ) {
413 0           $crypted .= $td->encrypt( substr $data_packet_string, 0+$i, 1 );
414             }
415 0           $td->end();
416             }
417 0           $evalok++;
418             };
419 0 0         warn "$@\n" if $@;
420             }
421             }
422              
423             # Mcrypt is fastest, but for some routines, there are alternatives if
424             # your perl Mcrypt <-> libmcrypt linkage isn't working.
425 0 0 0       if( ! $evalok && ! defined( $crypted ) && defined( $encryption_method )){
      0        
426 0 0 0       if( defined( $mcrypts{$encryption_method} ) && 1 == 2 ){
427 0           my $routine = '_encrypt_' . $mcrypts{$encryption_method};
428 0 0         if( $routine !~ /_$/ ){
429 0           eval {
430 0           $crypted = $routine->( $data_packet_string, $encryption_method, $iv_salt, $password );
431             };
432             }
433             }
434             }
435 0           return( $crypted );
436             }
437              
438              
439             'Yn anfon i maes an SOS';
440              
441             __END__