File Coverage

blib/lib/POE/Component/Client/NSCA.pm
Criterion Covered Total %
statement 305 361 84.4
branch 28 66 42.4
condition 5 22 22.7
subroutine 74 77 96.1
pod 1 1 100.0
total 413 527 78.3


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