Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:interstar001:Monitoringplugins
monitoring-plugins-centreon-linux-local
centreon_linux_local.pl
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File centreon_linux_local.pl of Package monitoring-plugins-centreon-linux-local
#!/usr/bin/perl # This chunk of stuff was generated by App::FatPacker. To find the original # file's code, look for the end of this BEGIN block or the string 'FATPACK' BEGIN { my %fatpacked; $fatpacked{"centreon/plugins/alternative/FatPackerOptions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_ALTERNATIVE_FATPACKEROPTIONS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::alternative::FatPackerOptions; use base qw(centreon::plugins::options); use strict; use warnings; use Pod::Usage; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; return $self; } sub display_help { my ($self, %options) = @_; my $stdout; foreach (@{$self->{pod_package}}) { { my $pp = $_->{package} . ".pm"; $pp =~ s{::}{/}g; my $content_class = $INC{$pp}->{$pp}; open my $str_fh, '<', \$content_class; local *STDOUT; open STDOUT, '>', \$stdout; pod2usage(-exitval => 'NOEXIT', -input => $str_fh, -verbose => 99, -sections => $_->{sections}); close $str_fh; } $self->{output}->add_option_msg(long_msg => $stdout) if (defined($stdout)); } } 1; CENTREON_PLUGINS_ALTERNATIVE_FATPACKEROPTIONS $fatpacked{"centreon/plugins/alternative/Getopt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_ALTERNATIVE_GETOPT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::alternative::Getopt; use strict; use warnings; use Exporter; use vars qw(@ISA @EXPORT @EXPORT_OK); @ISA = qw(Exporter); BEGIN { @EXPORT = qw(&GetOptions); @EXPORT_OK = qw(); } use vars @EXPORT, @EXPORT_OK; our $warn_message = 0; sub get_assigned_value { my (%options) = @_; if (!defined($options{val}) || $options{val} eq '') { # Add defined also. Hardened code: already see: $ARGV[6] = undef for example if ($options{pos} + 1 < $options{num_args} && defined($ARGV[$options{pos} + 1]) && $ARGV[$options{pos} + 1] !~ /^--/) { my $val = $ARGV[$options{pos} + 1]; splice @ARGV, $options{pos} + 1, 1; return ($options{num_args} - 1, $val); } else { return ($options{num_args}, ''); } } return ($options{num_args}, $options{val}); } sub GetOptions { my (%opts) = @_; my $search_str = ',' . join(',', keys %opts) . ','; my $num_args = scalar(@ARGV); for (my $i = 0; $i < $num_args;) { if (defined($ARGV[$i]) && $ARGV[$i] =~ /^--(.*?)(?:=|$)((?s).*)/) { my ($option, $value) = ($1, $2); # The special argument "--" forces an end of option-scanning. # All arguments placed after are stored in a list with the special option key '_double_dash_'. if ($option eq '' && $value eq '') { my @values = splice @ARGV, $i + 1, $num_args - $i - 1; push @{${$opts{'_double_dash_'}}}, @values; splice @ARGV, $i, 1; last; } # find type of option if ($search_str !~ /,((?:[^,]*?\|){0,}$option(?:\|.*?){0,}(:.*?){0,1}),/) { # for old format plugins (with run function) that not allowed list-counters options if($option =~ /list-counters/){ warn "list-counters option not available yet for this mode." if ($warn_message == 1); }else{ warn "Unknown option: $option" if ($warn_message == 1); } $i++; next; } my ($option_selected, $type_opt) = ($1, $2); if (!defined($type_opt)) { ($num_args, my $assigned) = get_assigned_value(num_args => $num_args, pos => $i, val => $value); if ($assigned ne '0') { ${$opts{$option_selected}} = 1; } } elsif ($type_opt =~ /:s$/) { ($num_args, my $assigned) = get_assigned_value(num_args => $num_args, pos => $i, val => $value); ${$opts{$option_selected}} = $assigned; } elsif ($type_opt =~ /:s\@$/) { ${$opts{$option . $type_opt}} = [] if (!defined(${$opts{$option . $type_opt}})); ($num_args, my $assigned) = get_assigned_value(num_args => $num_args, pos => $i, val => $value); push @{${$opts{$option_selected}}}, $assigned; } elsif ($type_opt =~ /:s\%$/) { ${$opts{$option . $type_opt}} = {} if (!defined(${$opts{$option . $type_opt}})); ($num_args, my $assigned) = get_assigned_value(num_args => $num_args, pos => $i, val => $value); if ($assigned =~ /^(.*?)=(.*)/) { ${$opts{$option_selected}}->{$1} = $2; } } splice @ARGV, $i, 1; $num_args--; } else { warn "argument $ARGV[$i] alone" if ($warn_message == 1 && $i != 0 && defined($ARGV[$i])); $i++; } } } 1; CENTREON_PLUGINS_ALTERNATIVE_GETOPT $fatpacked{"centreon/plugins/backend/http/curl.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_HTTP_CURL'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::http::curl; use strict; use warnings; use URI; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'curl-opt:s@' => { name => 'curl_opt' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'BACKEND CURL OPTIONS', once => 1); } $self->{output} = $options{output}; return $self; } sub check_options { my ($self, %options) = @_; centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Net::Curl::Easy', error_msg => "Cannot load module 'Net::Curl::Easy'." ); centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'centreon::plugins::backend::http::curlconstants', error_msg => "Cannot load module 'centreon::plugins::backend::http::curlconstants'." ); $self->{constant_cb} = \¢reon::plugins::backend::http::curlconstants::get_constant_value; foreach (('unknown_status', 'warning_status', 'critical_status')) { if (defined($options{request}->{$_})) { $options{request}->{$_} =~ s/%\{http_code\}/\$values->{code}/g; } } if (!defined($options{request}->{curl_opt})) { $options{request}->{curl_opt} = []; } } my $http_code_explained = { 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 450 => 'Timeout reached', # custom code 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' }; sub cb_debug { my ($easy, $type, $data, $uservar) = @_; my $msg = ''; if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_TEXT')) { $msg = sprintf("== Info: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_HEADER_OUT')) { $msg = sprintf("=> Send header: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_DATA_OUT')) { $msg = sprintf("=> Send data: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_HEADER_IN')) { $msg = sprintf("=> Recv header: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_DATA_IN')) { $msg = sprintf("=> Recv data: %s", $data); } return 0 if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_OUT')); return 0 if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_IN')); $uservar->{output}->output_add(long_msg => $msg, debug => 1); return 0; } sub curl_setopt { my ($self, %options) = @_; eval { $self->{curl_easy}->setopt($options{option}, $options{parameter}); }; if ($@) { $self->{output}->add_option_msg(short_msg => "curl setopt error: '" . $@ . "'."); $self->{output}->option_exit(); } } sub set_method { my ($self, %options) = @_; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => undef); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => undef); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPGET'), parameter => 1); if ($options{content_type_forced} == 1) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => $options{request}->{query_form_post}) if (defined($options{request}->{query_form_post})); } elsif (defined($options{request}->{post_params})) { my $uri_post = URI->new(); $uri_post->query_form($options{request}->{post_params}); push @{$options{headers}}, 'Content-Type: application/x-www-form-urlencoded'; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => $uri_post->query); } if ($options{request}->{method} eq 'GET') { return ; } if ($options{request}->{method} eq 'POST') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POST'), parameter => 1); } if ($options{request}->{method} eq 'PUT') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => $options{request}->{method}); } if ($options{request}->{method} eq 'DELETE') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => $options{request}->{method}); } if ($options{request}->{method} eq 'PATCH') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => $options{request}->{method}); } } sub set_auth { my ($self, %options) = @_; if (defined($options{request}->{credentials})) { if (defined($options{request}->{basic})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_BASIC')); } elsif (defined($options{request}->{ntlmv2})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_NTLM')); } elsif (defined($options{request}->{digest})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_DIGEST')); }else { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_ANY')); } $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_USERPWD'), parameter => $options{request}->{username} . ':' . $options{request}->{password}); } if (defined($options{request}->{cert_file}) && $options{request}->{cert_file} ne '') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERT'), parameter => $options{request}->{cert_file}); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLKEY'), parameter => $options{request}->{key_file}); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_KEYPASSWD'), parameter => $options{request}->{cert_pwd}); } $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERTTYPE'), parameter => "PEM"); if (defined($options{request}->{cert_pkcs12})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERTTYPE'), parameter => "P12"); } } sub set_form { my ($self, %options) = @_; if (!defined($self->{form_loaded})) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Net::Curl::Form', error_msg => "Cannot load module 'Net::Curl::Form'." ); $self->{form_loaded} = 1; } my $form = Net::Curl::Form->new(); foreach (@{$options{form}}) { my %args = (); $args{ $self->{constant_cb}->(name => 'CURLFORM_COPYNAME()') } = $_->{copyname} if (defined($_->{copyname})); $args{ $self->{constant_cb}->(name => 'CURLFORM_COPYCONTENTS()') } = $_->{copycontents} if (defined($_->{copycontents})); $form->add(%args); } $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPPOST()'), parameter => $form); } sub set_proxy { my ($self, %options) = @_; if (defined($options{request}->{proxyurl}) && $options{request}->{proxyurl} ne '') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_PROXY'), parameter => $options{request}->{proxyurl}); } if (defined($options{request}->{proxypac}) && $options{request}->{proxypac} ne '') { $self->{output}->add_option_msg(short_msg => 'Unsupported proxypac option'); $self->{output}->option_exit(); } } sub set_extra_curl_opt { my ($self, %options) = @_; my $entries = {}; foreach (@{$options{request}->{curl_opt}}) { my ($key, $value) = split /=>/; $key = centreon::plugins::misc::trim($key); if (!defined($entries->{$key})) { $entries->{$key} = { val => [], force_array => 0 }; } $value = centreon::plugins::misc::trim($value); if ($value =~ /^\[(.*)\]$/) { $entries->{$key}->{force_array} = 1; $value = centreon::plugins::misc::trim($1); } if ($value =~ /^CURLOPT|CURL/) { $value = $self->{constant_cb}->(name => $value); } push @{$entries->{$key}->{val}}, $value; } foreach (keys %$entries) { my $key = $_; if (/^CURLOPT|CURL/) { $key = $self->{constant_cb}->(name => $_); } if ($entries->{$_}->{force_array} == 1 || scalar(@{$entries->{$_}->{val}}) > 1) { $self->curl_setopt(option => $key, parameter => $entries->{$_}->{val}); } else { $self->curl_setopt(option => $key, parameter => pop @{$entries->{$_}->{val}}); } } } sub cb_get_header { my ($easy, $header, $uservar) = @_; $header =~ s/[\r\n]//g; if ($header =~ /^[\r\n]*$/) { $uservar->{nheaders}++; } else { $uservar->{response_headers}->[$uservar->{nheaders}] = {} if (!defined($uservar->{response_headers}->[$uservar->{nheaders}])); if ($header =~ /^(\S(?:.*?))\s*:\s*(.*)/) { my $header_name = lc($1); $uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name} = [] if (!defined($uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name})); push @{$uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name}}, $2; } else { $uservar->{response_headers}->[$uservar->{nheaders}]->{response_line} = $header; } } return length($_[1]); } sub request { my ($self, %options) = @_; if (!defined($self->{curl_easy})) { $self->{curl_easy} = Net::Curl::Easy->new(); } if ($self->{output}->is_debug()) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_DEBUGFUNCTION'), parameter => \&cb_debug); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_DEBUGDATA'), parameter => $self); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_VERBOSE'), parameter => 1); } if (defined($options{request}->{timeout}) && $options{request}->{timeout} =~ /\d/) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_TIMEOUT'), parameter => $options{request}->{timeout}); } if (defined($options{request}->{cookies_file})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_COOKIEFILE'), parameter => $options{request}->{cookies_file}); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_COOKIEJAR'), parameter => $options{request}->{cookies_file}); } $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FOLLOWLOCATION'), parameter => 1); if (defined($options{request}->{no_follow})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FOLLOWLOCATION'), parameter => 0); } my $url; if (defined($options{request}->{full_url})) { $url = $options{request}->{full_url}; } elsif (defined($options{request}->{port}) && $options{request}->{port} =~ /^[0-9]+$/) { $url = $options{request}->{proto}. "://" . $options{request}->{hostname} . ':' . $options{request}->{port} . $options{request}->{url_path}; } else { $url = $options{request}->{proto}. "://" . $options{request}->{hostname} . $options{request}->{url_path}; } if (defined($options{request}->{http_peer_addr}) && $options{request}->{http_peer_addr} ne '') { $url =~ /^(?:http|https):\/\/(.*?)(\/|\:|$)/; $self->{curl_easy}->setopt( $self->{constant_cb}->(name => 'CURLOPT_RESOLVE'), [$1 . ':' . $options{request}->{port_force} . ':' . $options{request}->{http_peer_addr}] ); } my $uri = URI->new($url); if (defined($options{request}->{get_params})) { $uri->query_form($options{request}->{get_params}); } $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_URL'), parameter => $uri); my $headers = []; my $content_type_forced = 0; foreach my $key (keys %{$options{request}->{headers}}) { push @$headers, $key . ':' . (defined($options{request}->{headers}->{$key}) ? $options{request}->{headers}->{$key} : ''); if ($key =~ /content-type/i) { $content_type_forced = 1; } } $self->set_method(%options, content_type_forced => $content_type_forced, headers => $headers); if (defined($options{request}->{form})) { $self->set_form(form => $options{request}->{form}); } if (scalar(@$headers) > 0) { $self->{curl_easy}->setopt($self->{constant_cb}->(name => 'CURLOPT_HTTPHEADER'), $headers); } if (defined($options{request}->{cacert_file}) && $options{request}->{cacert_file} ne '') { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CAINFO'), parameter => $options{request}->{cacert_file}); } if (defined($options{request}->{insecure})) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSL_VERIFYPEER'), parameter => 0); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSL_VERIFYHOST'), parameter => 0); } $self->set_auth(%options); $self->set_proxy(%options); $self->set_extra_curl_opt(%options); $self->{response_body} = ''; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FILE'), parameter => \$self->{response_body}); $self->{nheaders} = 0; $self->{response_headers} = [{}]; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERDATA'), parameter => $self); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERFUNCTION'), parameter => \&cb_get_header); if (defined($options{request}->{certinfo}) && $options{request}->{certinfo} == 1) { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CERTINFO'), parameter => 1); } $self->{response_code} = undef; eval { $self->{curl_easy}->perform(); }; if ($@) { my $err = $@; if (ref($@) eq "Net::Curl::Easy::Code") { my $num = $@; if ($num == $self->{constant_cb}->(name => 'CURLE_OPERATION_TIMEDOUT')) { $self->{response_code} = 450; } } if (!defined($self->{response_code})) { $self->{output}->add_option_msg(short_msg => 'curl perform error : ' . $err); $self->{output}->option_exit(); } } $self->{response_code} = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_RESPONSE_CODE')) if (!defined($self->{response_code})); # Check response my $status = 'ok'; if (defined($options{request}->{critical_status}) && $options{request}->{critical_status} ne '' && $self->{output}->test_eval(test => $options{request}->{critical_status}, values => { code => $self->{response_code} })) { $status = 'critical'; } elsif (defined($options{request}->{warning_status}) && $options{request}->{warning_status} ne '' && $self->{output}->test_eval(test => $options{request}->{warning_status}, values => { code => $self->{response_code} })) { $status = 'warning'; } elsif (defined($options{request}->{unknown_status}) && $options{request}->{unknown_status} ne '' && $self->{output}->test_eval(test => $options{request}->{unknown_status}, values => { code => $self->{response_code} })) { $status = 'unknown'; } if (!$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { my $short_msg = $self->{response_code} . ' ' . (defined($http_code_explained->{$self->{response_code}}) ? $http_code_explained->{$self->{response_code}} : 'unknown'); $self->{output}->output_add( severity => $status, short_msg => $short_msg ); $self->{output}->display(); $self->{output}->exit(); } return $self->{response_body}; } sub get_headers { my ($self, %options) = @_; my $headers = ''; foreach (keys %{$self->{response_headers}->[$options{nheader}]}) { next if (/response_line/); foreach my $value (@{$self->{response_headers}->[$options{nheader}]->{$_}}) { $headers .= "$_: " . $value . "\n"; } } return $headers; } sub get_first_header { my ($self, %options) = @_; if (!defined($options{name})) { return $self->get_headers(nheader => 0); } return undef if (!defined($self->{response_headers}->[0]->{ lc($options{name}) })); return wantarray ? @{$self->{response_headers}->[0]->{ lc($options{name}) }} : $self->{response_headers}->[0]->{ lc($options{name}) }->[0]; } sub get_header { my ($self, %options) = @_; if (!defined($options{name})) { return $self->get_headers(nheader => -1); } return undef if (!defined($self->{response_headers}->[-1]->{ lc($options{name}) })); return wantarray ? @{$self->{response_headers}->[-1]->{ lc($options{name}) }} : $self->{response_headers}->[-1]->{ lc($options{name}) }->[0]; } sub get_code { my ($self, %options) = @_; return $self->{response_code}; } sub get_message { my ($self, %options) = @_; return defined($http_code_explained->{$self->{response_code}}) ? $http_code_explained->{$self->{response_code}} : 'Unknown code'; } sub get_certificate { my ($self, %options) = @_; my $certs = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_CERTINFO')); return ('pem', $certs->[0]->{Cert}); } sub get_times { my ($self, %options) = @_; # TIME_T = 7.61.0 my $resolve = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_NAMELOOKUP_TIME')); my $connect = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_CONNECT_TIME')); my $appconnect = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_APPCONNECT_TIME')); my $start = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_STARTTRANSFER_TIME')); my $total = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_TOTAL_TIME')); my $times = { resolve => $resolve * 1000, connect => ($connect - $resolve) * 1000, transfer => ($total - $start) * 1000 }; if ($appconnect > 0) { $times->{tls} = ($appconnect - $connect) * 1000; $times->{processing} = ($start - $appconnect) * 1000; } else { $times->{processing} = ($start - $connect) * 1000; } return $times; } 1; =head1 NAME HTTP Curl backend layer. =head1 SYNOPSIS HTTP Curl backend layer. =head1 BACKEND CURL OPTIONS =over 8 =item B<--curl-opt> Set CURL Options (--curl-opt="CURLOPT_SSL_VERIFYPEER => 0" --curl-opt="CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_1" ). =back =head1 DESCRIPTION B<http>. =cut CENTREON_PLUGINS_BACKEND_HTTP_CURL $fatpacked{"centreon/plugins/backend/http/curlconstants.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_HTTP_CURLCONSTANTS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::http::curlconstants; use strict; use warnings; use Net::Curl::Easy qw(:constants); use Net::Curl::Form qw(:constants); sub get_constant_value { my (%options) = @_; return eval $options{name}; } 1; CENTREON_PLUGINS_BACKEND_HTTP_CURLCONSTANTS $fatpacked{"centreon/plugins/backend/http/lwp.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_HTTP_LWP'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::http::lwp; use strict; use warnings; use centreon::plugins::backend::http::useragent; use URI; use IO::Socket::SSL; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'ssl:s' => { name => 'ssl' }, 'ssl-opt:s@' => { name => 'ssl_opt' }, }); $options{options}->add_help(package => __PACKAGE__, sections => 'BACKEND LWP OPTIONS', once => 1); } $self->{output} = $options{output}; $self->{ua} = undef; $self->{debug_handlers} = 0; return $self; } sub check_options { my ($self, %options) = @_; foreach (('unknown_status', 'warning_status', 'critical_status')) { if (defined($options{request}->{$_})) { $options{request}->{$_} =~ s/%\{http_code\}/\$values->{code}/g; } } $self->{ssl_context} = ''; if (!defined($options{request}->{ssl_opt})) { $options{request}->{ssl_opt} = []; } if (defined($options{request}->{ssl}) && $options{request}->{ssl} ne '') { push @{$options{request}->{ssl_opt}}, 'SSL_version => ' . $options{request}->{ssl}; } if (defined($options{request}->{cert_file}) && !defined($options{request}->{cert_pkcs12})) { push @{$options{request}->{ssl_opt}}, 'SSL_use_cert => 1'; push @{$options{request}->{ssl_opt}}, 'SSL_cert_file => "' . $options{request}->{cert_file} . '"'; push @{$options{request}->{ssl_opt}}, 'SSL_key_file => "' . $options{request}->{key_file} . '"' if (defined($options{request}->{key_file})); push @{$options{request}->{ssl_opt}}, 'SSL_ca_file => "' . $options{request}->{cacert_file} . '"' if (defined($options{request}->{cacert_file})); } if ($options{request}->{insecure}) { push @{$options{request}->{ssl_opt}}, 'SSL_verify_mode => SSL_VERIFY_NONE'; } my $append = ''; foreach (@{$options{request}->{ssl_opt}}) { if ($_ ne '') { $self->{ssl_context} .= $append . $_; $append = ', '; } } } sub set_proxy { my ($self, %options) = @_; if (defined($options{request}->{proxypac}) && $options{request}->{proxypac} ne '') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'HTTP::ProxyPAC', error_msg => "Cannot load module 'HTTP::ProxyPAC'." ); my ($pac, $pac_uri); eval { if ($options{request}->{proxypac} =~ /^(http|https):\/\//) { $pac_uri = URI->new($options{request}->{proxypac}); $pac = HTTP::ProxyPAC->new($pac_uri); } else { $pac = HTTP::ProxyPAC->new($options{request}->{proxypac}); } }; if ($@) { $self->{output}->add_option_msg(short_msg => 'issue to load proxypac: ' . $@); $self->{output}->option_exit(); } my $res = $pac->find_proxy($options{url}); if (defined($res->direct) && $res->direct != 1) { my $proxy_uri = URI->new($res->proxy); $proxy_uri->userinfo($pac_uri->userinfo) if (defined($pac_uri->userinfo)); $self->{ua}->proxy(['http', 'https'], $proxy_uri->as_string); } } if (defined($options{request}->{proxyurl}) && $options{request}->{proxyurl} ne '') { my $proxyurl = $options{request}->{proxyurl}; if ($options{request}->{proto} eq "https" || (defined($options{request}->{full_url}) && $options{request}->{full_url} =~ /^https/)) { $proxyurl = 'connect://' . $2 if ($proxyurl =~ /^(http|https):\/\/(.*)/); } $self->{ua}->proxy(['http', 'https'], $proxyurl); } } sub request { my ($self, %options) = @_; my %user_agent_params = (keep_alive => 1); if (defined($options{request}->{certinfo}) && $options{request}->{certinfo} == 1) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'LWP::ConnCache', error_msg => "Cannot load module 'LWP::ConnCache'." ); $self->{cache} = LWP::ConnCache->new(); $self->{cache}->total_capacity(1); %user_agent_params = (conn_cache => $self->{cache}); } my $request_options = $options{request}; if (!defined($self->{ua})) { my $timeout; $timeout = $1 if (defined($request_options->{timeout}) && $request_options->{timeout} =~ /(\d+)/); $self->{ua} = centreon::plugins::backend::http::useragent->new( %user_agent_params, protocols_allowed => ['http', 'https'], timeout => $timeout, credentials => $request_options->{credentials}, username => $request_options->{username}, password => $request_options->{password} ); if (defined($request_options->{cookies_file})) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'HTTP::Cookies', error_msg => "Cannot load module 'HTTP::Cookies'." ); $self->{ua}->cookie_jar( HTTP::Cookies->new( file => $request_options->{cookies_file}, autosave => 1 ) ); } } if ($self->{output}->is_debug() && $self->{debug_handlers} == 0) { $self->{debug_handlers} = 1; $self->{ua}->add_handler('request_send', sub { my ($response, $ua, $handler) = @_; $self->{output}->output_add(long_msg => '======> request send', debug => 1); $self->{output}->output_add(long_msg => $response->as_string, debug => 1); return ; }); $self->{ua}->add_handler("response_done", sub { my ($response, $ua, $handler) = @_; $self->{output}->output_add(long_msg => '======> response done', debug => 1); $self->{output}->output_add(long_msg => $response->as_string, debug => 1); return ; }); } if (defined($request_options->{no_follow})) { $self->{ua}->requests_redirectable(undef); } else { $self->{ua}->requests_redirectable([ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH' ]); } if (defined($request_options->{http_peer_addr})) { push @LWP::Protocol::http::EXTRA_SOCK_OPTS, PeerAddr => $request_options->{http_peer_addr}; } my ($req, $url); if (defined($request_options->{full_url})) { $url = $request_options->{full_url}; } elsif (defined($request_options->{port}) && $request_options->{port} =~ /^[0-9]+$/) { $url = $request_options->{proto}. '://' . $request_options->{hostname} . ':' . $request_options->{port} . $request_options->{url_path}; } else { $url = $request_options->{proto}. '://' . $request_options->{hostname} . $request_options->{url_path}; } my $uri = URI->new($url); if (defined($request_options->{get_params})) { $uri->query_form($request_options->{get_params}); } $req = HTTP::Request->new($request_options->{method}, $uri); my $content_type_forced = 0; foreach my $key (keys %{$request_options->{headers}}) { $req->header($key => $request_options->{headers}->{$key}); if ($key =~ /content-type/i) { $content_type_forced = 1; } } if ($content_type_forced == 1) { $req->content($request_options->{query_form_post}); } elsif (defined($options{request}->{post_params})) { my $uri_post = URI->new(); $uri_post->query_form($request_options->{post_params}); $req->content_type('application/x-www-form-urlencoded'); $req->content($uri_post->query); } if (defined($request_options->{form})) { $self->{output}->add_option_msg(short_msg => 'unsupported form param'); $self->{output}->option_exit(); } if (defined($request_options->{credentials}) && defined($request_options->{ntlmv2})) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Authen::NTLM', error_msg => "Cannot load module 'Authen::NTLM'." ); Authen::NTLM::ntlmv2(1); } if (defined($request_options->{credentials}) && (defined($request_options->{basic}) || defined($request_options->{digest}))) { $req->authorization_basic($request_options->{username}, $request_options->{password}); } $self->set_proxy(request => $request_options, url => $url); if (defined($request_options->{cert_pkcs12}) && $request_options->{cert_file} ne '' && $request_options->{cert_pwd} ne '') { eval 'use Net::SSL'; die $@ if $@; $ENV{HTTPS_PKCS12_FILE} = $request_options->{cert_file}; $ENV{HTTPS_PKCS12_PASSWORD} = $request_options->{cert_pwd}; } if (defined($self->{ssl_context}) && $self->{ssl_context} ne '') { my $context = new IO::Socket::SSL::SSL_Context(eval $self->{ssl_context}); IO::Socket::SSL::set_default_context($context); } $self->{response} = $self->{ua}->request($req); $self->{response_code} = $self->{response}->code(); $self->{response_message} = $self->{response}->message(); $self->{headers} = $self->{response}->headers(); if ($self->{response_code} == 500) { my $client_warning = $self->get_header(name => 'Client-Warning'); if (defined($client_warning) && $client_warning eq 'Internal response' && $self->{response_message} =~ /connection timed out/mi) { $self->{response_code} = 450; $self->{response_message} = 'Timeout reached'; } } # Check response my $status = 'ok'; if (defined($request_options->{critical_status}) && $request_options->{critical_status} ne '' && $self->{output}->test_eval(test => $request_options->{critical_status}, values => { code => $self->{response_code} })) { $status = 'critical'; } elsif (defined($request_options->{warning_status}) && $request_options->{warning_status} ne '' && $self->{output}->test_eval(test => $request_options->{warning_status}, values => { code => $self->{response_code} })) { $status = 'warning'; } elsif (defined($request_options->{unknown_status}) && $request_options->{unknown_status} ne '' && $self->{output}->test_eval(test => $request_options->{unknown_status}, values => { code => $self->{response_code} })) { $status = 'unknown'; } if (!$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { my $short_msg = $self->{response}->status_line; if ($short_msg =~ /^401/) { $short_msg .= ' (' . $1 . ' authentication expected)' if (defined($self->{response}->www_authenticate) && $self->{response}->www_authenticate =~ /(\S+)/); } $self->{output}->output_add( severity => $status, short_msg => $short_msg ); $self->{output}->display(); $self->{output}->exit(); } return $self->{response}->content; } sub get_headers { my ($self, %options) = @_; my $headers = ''; foreach ($options{response}->header_field_names()) { my $value = $options{response}->header($_); $headers .= "$_: " . (defined($value) ? $value : '') . "\n"; } return $headers; } sub get_first_header { my ($self, %options) = @_; my @redirects = $self->{response}->redirects(); if (!defined($options{name})) { return $self->get_headers(response => defined($redirects[0]) ? $redirects[0] : $self->{response}); } return defined($redirects[0]) ? $redirects[0]->headers()->header($options{name}) : $self->{headers}->header($options{name}) ; } sub get_header { my ($self, %options) = @_; if (!defined($options{name})) { return $self->get_headers(response => $self->{response}); } return $self->{headers}->header($options{name}); } sub get_code { my ($self, %options) = @_; return $self->{response_code}; } sub get_message { my ($self, %options) = @_; return $self->{response_message}; } sub get_certificate { my ($self, %options) = @_; my ($con) = $self->{cache}->get_connections('https'); return ('socket', $con); } sub get_times { my ($self, %options) = @_; return undef; } 1; =head1 NAME HTTP LWP backend layer. =head1 SYNOPSIS HTTP LWP backend layer. =head1 BACKEND LWP OPTIONS =over 8 =item B<--ssl-opt> Set SSL Options (--ssl-opt="SSL_version => TLSv1" --ssl-opt="SSL_verify_mode => SSL_VERIFY_NONE"). =item B<--ssl> Set SSL version (--ssl=TLSv1). =back =head1 DESCRIPTION B<http>. =cut CENTREON_PLUGINS_BACKEND_HTTP_LWP $fatpacked{"centreon/plugins/backend/http/useragent.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_HTTP_USERAGENT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::http::useragent; use strict; use warnings; use base 'LWP::UserAgent'; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; $self = LWP::UserAgent::new(@_); $self->agent("centreon::plugins::backend::http::useragent"); $self->{credentials} = $options{credentials} if defined($options{credentials}); $self->{username} = $options{username} if defined($options{username}); $self->{password} = $options{password} if defined($options{password}); return $self; } sub get_basic_credentials { my($self, $realm, $uri, $proxy) = @_; return if $proxy; return $self->{username}, $self->{password} if $self->{credentials} and wantarray; return $self->{username} . ':' . $self->{password} if $self->{credentials}; return undef; } 1; CENTREON_PLUGINS_BACKEND_HTTP_USERAGENT $fatpacked{"centreon/plugins/backend/ssh/libssh.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_SSH_LIBSSH'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::ssh::libssh; use strict; use warnings; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'libssh-strict-connect' => { name => 'libssh_strict_connect' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'BACKEND LIBSSH OPTIONS', once => 1); } $self->{connected} = 0; $self->{output} = $options{output}; return $self; } sub check_options { my ($self, %options) = @_; centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Libssh::Session', error_msg => "Cannot load module 'Libssh::Session'." ); centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'centreon::plugins::backend::ssh::libsshconstants', error_msg => "Cannot load module 'centreon::plugins::backend::ssh::libsshconstants'." ); $self->{constant_cb} = \¢reon::plugins::backend::ssh::libsshconstants::get_constant_value; if (!defined($self->{ssh})) { $self->{ssh} = Libssh::Session->new(); } $self->{ssh_port} = defined($options{option_results}->{ssh_port}) && $options{option_results}->{ssh_port} =~ /(\d+)/ ? $1 : 22; $self->{ssh}->options(port => $self->{ssh_port}); $self->{ssh_username} = $options{option_results}->{ssh_username}; $self->{ssh_password} = $options{option_results}->{ssh_password}; $self->{ssh}->options(identity => $options{option_results}->{ssh_priv_key}) if (defined($options{option_results}->{ssh_priv_key}) && $options{option_results}->{ssh_priv_key} ne ''); $self->{ssh}->options(user => $options{option_results}->{ssh_username}) if (defined($options{option_results}->{ssh_username}) && $options{option_results}->{ssh_username} ne ''); $self->{ssh_strict_connect} = defined($options{option_results}->{libssh_strict_connect}) ? 0 : 1; } sub connect { my ($self, %options) = @_; return if ($self->{connected} == 1); $self->{ssh}->options(host => $options{hostname}); if ($self->{ssh}->connect(SkipKeyProblem => $self->{ssh_strict_connect}) != $self->{constant_cb}->(name => 'SSH_OK')) { $self->{output}->add_option_msg(short_msg => 'connect issue: ' . $self->{ssh}->error()); $self->{output}->option_exit(); } if ($self->{ssh}->auth_publickey_auto() != $self->{constant_cb}->(name => 'SSH_AUTH_SUCCESS')) { if (defined($self->{ssh_username}) && $self->{ssh_username} ne '' && defined($self->{ssh_password}) && $self->{ssh_password} ne '' && $self->{ssh}->auth_password(password => $self->{ssh_password}) == $self->{constant_cb}->(name => 'SSH_AUTH_SUCCESS')) { $self->{connected} = 1; return ; } my $msg_error = $self->{ssh}->error(GetErrorSession => 1); $self->{output}->add_option_msg(short_msg => sprintf("auth issue: %s", defined($msg_error) && $msg_error ne '' ? $msg_error : 'pubkey issue')); $self->{output}->option_exit(); } $self->{connected} = 1; } sub execute { my ($self, %options) = @_; if (defined($options{timeout}) && $options{timeout} =~ /(\d+)/) { $self->{ssh}->options(timeout => $options{timeout}); } $self->connect(hostname => $options{hostname}); my $cmd = ''; $cmd = 'sudo ' if (defined($options{sudo})); $cmd .= $options{command_path} . '/' if (defined($options{command_path})); $cmd .= $options{command} . ' ' if (defined($options{command})); $cmd .= $options{command_options} if (defined($options{command_options})); my $ret; if (!defined($options{ssh_pipe}) || $options{ssh_pipe} == 0) { $ret = $self->{ssh}->execute_simple( cmd => $cmd, timeout => $options{timeout}, timeout_nodata => $options{timeout} ); } else { $ret = $self->{ssh}->execute_simple( input_data => $cmd, timeout => $options{timeout}, timeout_nodata => $options{timeout} ); } $self->{output}->output_add(long_msg => $ret->{stdout}, debug => 1) if (defined($ret->{stdout})); $self->{output}->output_add(long_msg => $ret->{stderr}, debug => 1) if (defined($ret->{stderr})); my ($content, $exit_code); if ($ret->{exit} == $self->{constant_cb}->(name => 'SSH_OK')) { $content = $ret->{stdout}; $exit_code = $ret->{exit_code}; } elsif ($ret->{exit} == $self->{constant_cb}->(name => 'SSH_AGAIN')) { # AGAIN means timeout $self->{output}->add_option_msg(short_msg => sprintf('command execution timeout')); $self->{output}->option_exit(); } else { $self->{output}->add_option_msg(short_msg => sprintf( 'command execution error: %s', $self->{ssh}->error(GetErrorSession => 1) ) ); $self->{output}->option_exit(); } if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1) { # Last failed login: Tue Feb 25 09:30:20 EST 2020 from 10.40.1.160 on ssh:notty # There was 1 failed login attempt since the last successful login. $content =~ s/^(?:Last failed login:|There was.*?failed login).*?\n//msg; } if ($exit_code != 0 && (!defined($options{no_quit}) || $options{no_quit} != 1)) { $self->{output}->add_option_msg(short_msg => sprintf('command execution error [exit code: %s]', $exit_code)); $self->{output}->option_exit(); } return ($content, $exit_code); } 1; =head1 NAME libssh backend. =head1 SYNOPSIS libssh backend. =head1 BACKEND LIBSSH OPTIONS =over 8 =item B<--libssh-strict-connect> Connection won't be OK even if there is a problem (server known changed or server found other) with the ssh server. =back =head1 DESCRIPTION B<libssh>. =cut CENTREON_PLUGINS_BACKEND_SSH_LIBSSH $fatpacked{"centreon/plugins/backend/ssh/libsshconstants.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_SSH_LIBSSHCONSTANTS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::ssh::libsshconstants; use strict; use warnings; use Libssh::Session qw(:all); sub get_constant_value { my (%options) = @_; return eval $options{name}; } 1; CENTREON_PLUGINS_BACKEND_SSH_LIBSSHCONSTANTS $fatpacked{"centreon/plugins/backend/ssh/plink.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_SSH_PLINK'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::ssh::plink; use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'plink-command:s' => { name => 'plink_command' }, 'plink-path:s' => { name => 'plink_path' }, 'plink-option:s@' => { name => 'plink_option' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'BACKEND PLINK OPTIONS', once => 1); } $self->{output} = $options{output}; return $self; } sub check_options { my ($self, %options) = @_; $self->{ssh_command} = defined($options{option_results}->{plink_command}) && $options{option_results}->{plink_command} ne '' ? $options{option_results}->{plink_command} : 'plink'; $self->{ssh_path} = $options{option_results}->{plink_path}; $self->{ssh_option} = defined($options{option_results}->{plink_option}) ? $options{option_results}->{plink_option} : []; $self->{ssh_port} = defined($options{option_results}->{ssh_port}) && $options{option_results}->{ssh_port} =~ /(\d+)/ ? $1 : 22; $self->{ssh_priv_key} = $options{option_results}->{ssh_priv_key}; $self->{ssh_username} = $options{option_results}->{ssh_username}; $self->{ssh_password} = $options{option_results}->{ssh_password}; centreon::plugins::misc::check_security_command( output => $self->{output}, command => $options{option_results}->{plink_command}, command_options => join('', @{$self->{ssh_option}}), command_path => $self->{ssh_path} ); push @{$self->{ssh_option}}, '-batch'; push @{$self->{ssh_option}}, '-l=' . $self->{ssh_username} if (defined($self->{ssh_username}) && $self->{ssh_username} ne ''); push @{$self->{ssh_option}}, '-pw=' . $self->{ssh_password} if (defined($self->{ssh_password}) && $self->{ssh_password} ne ''); push @{$self->{ssh_option}}, '-P=' . $self->{ssh_port} if (defined($self->{ssh_port}) && $self->{ssh_port} ne ''); push @{$self->{ssh_option}}, '-i=' . $self->{ssh_priv_key} if (defined($self->{ssh_priv_key}) && $self->{ssh_priv_key} ne ''); } sub execute { my ($self, %options) = @_; push @{$self->{ssh_option}}, '-T' if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1); $options{command} .= $options{cmd_exit} if (defined($options{cmd_exit}) && $options{cmd_exit} ne ''); my ($content, $exit_code) = centreon::plugins::misc::execute( output => $self->{output}, sudo => $options{sudo}, command => $options{command}, command_path => $options{command_path}, command_options => $options{command_options}, ssh_pipe => $options{ssh_pipe}, options => { remote => 1, ssh_address => $options{hostname}, ssh_command => $self->{ssh_command}, ssh_path => $self->{ssh_path}, ssh_option => $self->{ssh_option}, timeout => $options{timeout} }, no_quit => $options{no_quit} ); if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1) { # Using username "root". $content =~ s/^Using username ".*?"\.\n//msg; } # plink exit code is 0 even connection is abandoned if ($content =~ /server.*?key fingerprint.*connection abandoned/msi) { $self->{output}->add_option_msg(short_msg => 'please connect with plink command to your host and accept the server host key'); $self->{output}->option_exit(); } return ($content, $exit_code); } 1; =head1 NAME plink backend. =head1 SYNOPSIS plink backend. =head1 BACKEND PLINK OPTIONS =over 8 =item B<--plink-command> plink command (default: 'plink'). =item B<--plink-path> plink command path (default: none) =item B<--plink-option> Specify plink options (example: --plink-option='-T'). =back =head1 DESCRIPTION B<plink>. =cut CENTREON_PLUGINS_BACKEND_SSH_PLINK $fatpacked{"centreon/plugins/backend/ssh/sshcli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_BACKEND_SSH_SSHCLI'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::backend::ssh::sshcli; use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'sshcli-command:s' => { name => 'sshcli_command' }, 'sshcli-path:s' => { name => 'sshcli_path' }, 'sshcli-option:s@' => { name => 'sshcli_option' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'BACKEND SSHCLI OPTIONS', once => 1); } $self->{output} = $options{output}; return $self; } sub check_options { my ($self, %options) = @_; $self->{ssh_command} = defined($options{option_results}->{sshcli_command}) && $options{option_results}->{sshcli_command} ne '' ? $options{option_results}->{sshcli_command} : 'ssh'; $self->{ssh_path} = $options{option_results}->{sshcli_path}; $self->{ssh_option} = defined($options{option_results}->{sshcli_option}) ? $options{option_results}->{sshcli_option} : []; $self->{ssh_port} = defined($options{option_results}->{ssh_port}) && $options{option_results}->{ssh_port} =~ /(\d+)/ ? $1 : 22; $self->{ssh_priv_key} = $options{option_results}->{ssh_priv_key}; $self->{ssh_username} = $options{option_results}->{ssh_username}; if (defined($options{option_results}->{ssh_password}) && $options{option_results}->{ssh_password} ne '') { $self->{output}->add_option_msg(short_msg => 'sshcli backend cannot use ssh password. please use backend plink or libssh'); $self->{output}->option_exit(); } centreon::plugins::misc::check_security_command( output => $self->{output}, command => $options{option_results}->{sshcli_command}, command_options => join('', @{$self->{ssh_option}}), command_path => $self->{ssh_path} ); push @{$self->{ssh_option}}, '-o=BatchMode=yes'; push @{$self->{ssh_option}}, '-l=' . $self->{ssh_username} if (defined($self->{ssh_username}) && $self->{ssh_username} ne ''); push @{$self->{ssh_option}}, '-p=' . $self->{ssh_port} if (defined($self->{ssh_port}) && $self->{ssh_port} ne ''); push @{$self->{ssh_option}}, '-i=' . $self->{ssh_priv_key} if (defined($self->{ssh_priv_key}) && $self->{ssh_priv_key} ne ''); } sub execute { my ($self, %options) = @_; push @{$self->{ssh_option}}, '-T' if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1); $options{command} .= $options{cmd_exit} if (defined($options{cmd_exit}) && $options{cmd_exit} ne ''); my ($content, $exit_code) = centreon::plugins::misc::execute( output => $self->{output}, sudo => $options{sudo}, command => $options{command}, command_path => $options{command_path}, command_options => $options{command_options}, ssh_pipe => $options{ssh_pipe}, options => { remote => 1, ssh_address => $options{hostname}, ssh_command => $self->{ssh_command}, ssh_path => $self->{ssh_path}, ssh_option => $self->{ssh_option}, timeout => $options{timeout} }, no_quit => $options{no_quit} ); if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1) { # Last failed login: Tue Feb 25 09:30:20 EST 2020 from 10.40.1.160 on ssh:notty # There was 1 failed login attempt since the last successful login. $content =~ s/^(?:Last failed login:|There was.*?failed login).*?\n//msg; } return ($content, $exit_code); } 1; =head1 NAME ssh cli backend. =head1 SYNOPSIS ssh cli backend. =head1 BACKEND SSHCLI OPTIONS =over 8 =item B<--sshcli-command> ssh command (default: 'ssh'). =item B<--sshcli-path> ssh command path (default: none) =item B<--sshcli-option> Specify ssh cli options (example: --sshcli-option='-o=StrictHostKeyChecking=no'). =back =head1 DESCRIPTION B<sshcli>. =cut CENTREON_PLUGINS_BACKEND_SSH_SSHCLI $fatpacked{"centreon/plugins/http.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_HTTP'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::http; use strict; use warnings; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'http-peer-addr:s' => { name => 'http_peer_addr' }, 'proxyurl:s' => { name => 'proxyurl' }, 'proxypac:s' => { name => 'proxypac' }, 'insecure' => { name => 'insecure' }, 'http-backend:s' => { name => 'http_backend' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'HTTP GLOBAL OPTIONS'); } centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'centreon::plugins::backend::http::lwp', error_msg => "Cannot load module 'centreon::plugins::backend::http::lwp'." ); $self->{backend_lwp} = centreon::plugins::backend::http::lwp->new(%options); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'centreon::plugins::backend::http::curl', error_msg => "Cannot load module 'centreon::plugins::backend::http::curl'." ); $self->{backend_curl} = centreon::plugins::backend::http::curl->new(%options); $self->{default_backend} = defined($options{default_backend}) && $options{default_backend} ne '' ? $options{default_backend} : 'lwp'; $self->{output} = $options{output}; $self->{options} = { proto => 'http', url_path => '/', timeout => 5, method => 'GET', unknown_status => '%{http_code} < 200 or %{http_code} >= 300', warning_status => undef, critical_status => undef, }; $self->{add_headers} = {}; return $self; } sub set_options { my ($self, %options) = @_; $self->{options} = { %{$self->{options}} }; foreach (keys %options) { $self->{options}->{$_} = $options{$_} if (defined($options{$_})); } } sub add_header { my ($self, %options) = @_; $self->{add_headers}->{$options{key}} = $options{value}; } sub remove_header { my ($self, %options) = @_; delete $self->{add_headers}->{$options{key}} if (defined($self->{add_headers}->{$options{key}})); } sub check_options { my ($self, %options) = @_; $options{request}->{http_backend} = $self->{default_backend} if (!defined($options{request}->{http_backend}) || $options{request}->{http_backend} eq ''); $self->{http_backend} = $options{request}->{http_backend}; if ($self->{http_backend} !~ /^\s*lwp|curl\s*$/i) { $self->{output}->add_option_msg(short_msg => "Unsupported http backend specified '" . $self->{http_backend} . "'."); $self->{output}->option_exit(); } if (defined($options{request}->{$self->{http_backend} . '_backend_options'})) { foreach (keys %{$options{request}->{$self->{http_backend} . '_backend_options'}}) { $options{request}->{$_} = $options{request}->{$self->{http_backend} . '_backend_options'}->{$_}; } } if (($options{request}->{proto} ne 'http') && ($options{request}->{proto} ne 'https')) { $self->{output}->add_option_msg(short_msg => "Unsupported protocol specified: '(" . $options{request}->{proto} . ")'. . Use either https or http."); $self->{output}->option_exit(); } if (!defined($options{request}->{hostname})) { $self->{output}->add_option_msg(short_msg => "Please set the hostname option"); $self->{output}->option_exit(); } if ((defined($options{request}->{credentials})) && (!defined($options{request}->{username}) || !defined($options{request}->{password}))) { $self->{output}->add_option_msg(short_msg => "You need to set --username= and --password= options when --credentials is used"); $self->{output}->option_exit(); } if ((defined($options{request}->{cert_pkcs12})) && (!defined($options{request}->{cert_file}) && !defined($options{request}->{cert_pwd}))) { $self->{output}->add_option_msg(short_msg => "You need to set --cert-file= and --cert-pwd= options when --pkcs12 is used"); $self->{output}->option_exit(); } $options{request}->{port_force} = $self->get_port(); $options{request}->{headers} = {}; if (defined($options{request}->{header})) { foreach (@{$options{request}->{header}}) { if (/^(:.+?|.+?):(.*)/) { $options{request}->{headers}->{$1} = $2; } } } foreach (keys %{$self->{add_headers}}) { $options{request}->{headers}->{$_} = $self->{add_headers}->{$_}; } foreach my $method (('get', 'post')) { if (defined($options{request}->{$method . '_param'})) { $options{request}->{$method . '_params'} = {}; foreach (@{$options{request}->{$method . '_param'}}) { if (/^([^=]+)={0,1}(.*)$/s) { my $key = $1; my $value = defined($2) ? $2 : 1; if (defined($options{request}->{$method . '_params'}->{$key})) { if (ref($options{request}->{$method . '_params'}->{$key}) ne 'ARRAY') { $options{request}->{$method . '_params'}->{$key} = [ $options{request}->{$method . '_params'}->{$key} ]; } push @{$options{request}->{$method . '_params'}->{$key}}, $value; } else { $options{request}->{$method . '_params'}->{$key} = $value; } } } } } $self->{'backend_' . $self->{http_backend}}->check_options(%options); } sub get_port { my ($self, %options) = @_; my $port = ''; if (defined($self->{options}->{port}) && $self->{options}->{port} ne '') { $port = $self->{options}->{port}; } else { $port = 80 if ($self->{options}->{proto} eq 'http'); $port = 443 if ($self->{options}->{proto} eq 'https'); } return $port; } sub get_port_request { my ($self, %options) = @_; my $port = ''; if (defined($self->{options}->{port}) && $self->{options}->{port} ne '') { $port = $self->{options}->{port}; } return $port; } sub request { my ($self, %options) = @_; my $request_options = { %{$self->{options}} }; foreach (keys %options) { $request_options->{$_} = $options{$_} if (defined($options{$_})); } $self->check_options(request => $request_options); return $self->{'backend_' . $self->{http_backend}}->request(request => $request_options); } sub get_first_header { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_first_header(%options); } sub get_header { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_header(%options); } sub get_code { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_code(); } sub get_message { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_message(); } sub get_certificate { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_certificate(); } sub get_times { my ($self, %options) = @_; return $self->{'backend_' . $self->{http_backend}}->get_times(); } 1; =head1 NAME HTTP abstraction layer. =head1 SYNOPSIS HTTP abstraction layer for lwp and curl backends =head1 HTTP GLOBAL OPTIONS =over 8 =item B<--http-peer-addr> Set the address you want to connect to. Useful if hostname is only a vhost, to avoid IP resolution. =item B<--proxyurl> Proxy URL. Example: http://my.proxy:3128 =item B<--proxypac> Proxy pac file (can be a URL or a local file). =item B<--insecure> Accept insecure SSL connections. =item B<--http-backend> Perl library to use for HTTP transactions. Possible values are: lwp (default) and curl. =back =head1 DESCRIPTION B<http>. =cut CENTREON_PLUGINS_HTTP $fatpacked{"centreon/plugins/misc.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_MISC'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::misc; use strict; use warnings; use utf8; use JSON::XS; sub execute { my (%options) = @_; if ($^O eq 'MSWin32') { return windows_execute(%options, timeout => $options{options}->{timeout}); } else { return unix_execute(%options); } } sub windows_execute { my (%options) = @_; my $result; my ($stdout, $pid, $ended) = (''); my ($exit_code, $cmd); $cmd = $options{command_path} . '/' if (defined($options{command_path})); $cmd .= $options{command} . ' ' if (defined($options{command})); $cmd .= $options{command_options} if (defined($options{command_options})); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'Win32::Job', error_msg => "Cannot load module 'Win32::Job'." ); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'Time::HiRes', error_msg => "Cannot load module 'Time::HiRes'." ); $| = 1; pipe FROM_CHILD, TO_PARENT or do { $options{output}->add_option_msg(short_msg => "Internal error: can't create pipe from child to parent: $!"); $options{output}->option_exit(); }; my $job = Win32::Job->new; my $stderr = 'NUL'; $stderr = \*TO_PARENT if ($options{output}->is_debug()); if (!($pid = $job->spawn(undef, $cmd, { stdin => 'NUL', stdout => \*TO_PARENT, stderr => $stderr }))) { $options{output}->add_option_msg(short_msg => "Internal error: execution issue: $^E"); $options{output}->option_exit(); } close TO_PARENT; my $ein = ''; vec($ein, fileno(FROM_CHILD), 1) = 1; $job->watch( sub { my ($buffer); my $time = $options{timeout}; my $last_time = Time::HiRes::time(); $ended = 0; while (select($ein, undef, undef, $options{timeout})) { if (sysread(FROM_CHILD, $buffer, 16384)) { $buffer =~ s/\r//g; $stdout .= $buffer; } else { $ended = 1; last; } $options{timeout} -= Time::HiRes::time() - $last_time; last if ($options{timeout} <= 0); $last_time = Time::HiRes::time(); } return 1 if ($ended == 0); return 0; }, 0.1 ); $result = $job->status; close FROM_CHILD; if ($ended == 0) { $options{output}->add_option_msg(short_msg => 'Command too long to execute (timeout)...'); $options{output}->option_exit(); } chomp $stdout; if (defined($options{no_quit}) && $options{no_quit} == 1) { return ($stdout, $result->{$pid}->{exitcode}); } if ($result->{$pid}->{exitcode} != 0) { $stdout =~ s/\n/ - /g; $options{output}->add_option_msg(short_msg => "Command error: $stdout"); $options{output}->option_exit(); } return ($stdout, $result->{$pid}->{exitcode}); } sub unix_execute { my (%options) = @_; my $cmd = ''; my $args = []; my ($lerror, $stdout, $exit_code); my $redirect_stderr = 1; $redirect_stderr = $options{redirect_stderr} if (defined($options{redirect_stderr})); my $wait_exit = 1; $wait_exit = $options{wait_exit} if (defined($options{wait_exit})); # Build command line # Can choose which command is done remotely (can filter and use local file) if (defined($options{options}->{remote}) && ($options{options}->{remote} eq '' || !defined($options{label}) || $options{label} =~ /$options{options}->{remote}/)) { my $sub_cmd; $cmd = $options{options}->{ssh_path} . '/' if (defined($options{options}->{ssh_path})); $cmd .= $options{options}->{ssh_command} if (defined($options{options}->{ssh_command})); foreach (@{$options{options}->{ssh_option}}) { if (/^(.*?)(?:=(.*))?$/) { push @$args, $1 if (defined($1)); push @$args, $2 if (defined($2)); } } if (defined($options{options}->{ssh_address}) && $options{options}->{ssh_address} ne '') { push @$args, $options{options}->{ssh_address}; } else { push @$args, $options{options}->{hostname}; } $sub_cmd = 'sudo ' if (defined($options{sudo})); $sub_cmd .= $options{command_path} . '/' if (defined($options{command_path})); $sub_cmd .= $options{command} . ' ' if (defined($options{command})); $sub_cmd .= $options{command_options} if (defined($options{command_options})); # On some equipment. Cannot get a pseudo terminal if (defined($options{ssh_pipe}) && $options{ssh_pipe} == 1) { $cmd = "echo '" . $sub_cmd . "' | " . $cmd . ' ' . join(' ', @$args); ($lerror, $stdout, $exit_code) = backtick( command => $cmd, timeout => $options{options}->{timeout}, wait_exit => $wait_exit, redirect_stderr => $redirect_stderr ); } else { ($lerror, $stdout, $exit_code) = backtick( command => $cmd, arguments => [@$args, $sub_cmd], timeout => $options{options}->{timeout}, wait_exit => $wait_exit, redirect_stderr => $redirect_stderr ); } } else { $cmd = 'sudo ' if (defined($options{sudo})); $cmd .= $options{command_path} . '/' if (defined($options{command_path})); $cmd .= $options{command} . ' ' if (defined($options{command})); $cmd .= $options{command_options} if (defined($options{command_options})); ($lerror, $stdout, $exit_code) = backtick( command => $cmd, timeout => $options{options}->{timeout}, wait_exit => $wait_exit, redirect_stderr => $redirect_stderr ); } if (defined($options{options}->{show_output}) && ($options{options}->{show_output} eq '' || (defined($options{label}) && $options{label} eq $options{options}->{show_output}))) { print $stdout; exit $exit_code; } $stdout =~ s/\r//g; if ($lerror <= -1000) { $options{output}->add_option_msg(short_msg => $stdout); $options{output}->option_exit(); } if (defined($options{no_quit}) && $options{no_quit} == 1) { return ($stdout, $exit_code); } if ($exit_code != 0 && (!defined($options{no_errors}) || !defined($options{no_errors}->{$exit_code}))) { $stdout =~ s/\n/ - /g; $options{output}->add_option_msg(short_msg => "Command error: $stdout"); $options{output}->option_exit(); } return $stdout; } sub mymodule_load { my (%options) = @_; my $file; ($file = ($options{module} =~ /\.pm$/ ? $options{module} : $options{module} . '.pm')) =~ s{::}{/}g; eval { local $SIG{__DIE__} = 'IGNORE'; require $file; $file =~ s{/}{::}g; $file =~ s/\.pm$//; }; if ($@) { return 1 if (defined($options{no_quit}) && $options{no_quit} == 1); $options{output}->add_option_msg(long_msg => $@); $options{output}->add_option_msg(short_msg => $options{error_msg}); $options{output}->option_exit(); } return wantarray ? (0, $file) : 0; } sub backtick { my %arg = ( command => undef, arguments => [], timeout => 30, wait_exit => 0, redirect_stderr => 0, @_, ); my @output; my $pid; my $return_code; my $sig_do; if ($arg{wait_exit} == 0) { $sig_do = 'IGNORE'; $return_code = undef; } else { $sig_do = 'DEFAULT'; } local $SIG{CHLD} = $sig_do; $SIG{TTOU} = 'IGNORE'; $| = 1; if (!defined($pid = open( KID, "-|" ))) { return (-1001, "Cant fork: $!", -1); } if ($pid) { eval { local $SIG{ALRM} = sub { die "Timeout by signal ALARM\n"; }; alarm( $arg{timeout} ); while (<KID>) { chomp; push @output, $_; } alarm(0); }; if ($@) { if ($pid != -1) { kill -9, $pid; } alarm(0); return (-1000, 'Command too long to execute (timeout)...', -1); } else { if ($arg{wait_exit} == 1) { # We're waiting the exit code waitpid($pid, 0); $return_code = ($? >> 8); } close KID; } } else { # child # set the child process to be a group leader, so that # kill -9 will kill it and all its descendents # We have ignore SIGTTOU to let write background processes setpgrp( 0, 0 ); if ($arg{redirect_stderr} == 1) { open STDERR, '>&STDOUT'; } if (scalar(@{$arg{arguments}}) <= 0) { exec($arg{command}); } else { exec($arg{command}, @{$arg{arguments}}); } # Exec is in error. No such command maybe. exit(127); } return (0, join("\n", @output), $return_code); } sub is_empty { my $value = shift; if (!defined($value) or $value eq '') { return 1; } return 0; } sub trim { my ($value) = $_[0]; # Sometimes there is a null character $value =~ s/\x00$//; $value =~ s/^[ \t\n]+//; $value =~ s/[ \t\n]+$//; return $value; } sub powershell_encoded { require Encode; require MIME::Base64; my $bytes = Encode::encode('utf16LE', $_[0]); return MIME::Base64::encode_base64($bytes, ''); } sub powershell_escape { my ($value) = $_[0]; $value =~ s/`/``/g; $value =~ s/#/`#/g; $value =~ s/'/`'/g; $value =~ s/"/`"/g; return $value; } sub minimal_version { my ($version_src, $version_dst) = @_; # No Version. We skip if (!defined($version_src) || !defined($version_dst) || $version_src !~ /^[0-9]+(?:\.[0-9\.]+)*$/ || $version_dst !~ /^[0-9x]+(?:\.[0-9x]+)*$/) { return 1; } my @version_src = split /\./, $version_src; my @versions = split /\./, $version_dst; for (my $i = 0; $i < scalar(@versions); $i++) { return 1 if ($versions[$i] eq 'x'); return 1 if (!defined($version_src[$i])); $version_src[$i] =~ /^([0-9]*)/; next if ($versions[$i] == int($1)); return 0 if ($versions[$i] > int($1)); return 1 if ($versions[$i] < int($1)); } return 1; } sub change_seconds { my %options = @_; my ($str, $str_append) = ('', ''); my $periods = [ { unit => 'y', value => 31556926 }, { unit => 'M', value => 2629743 }, { unit => 'w', value => 604800 }, { unit => 'd', value => 86400 }, { unit => 'h', value => 3600 }, { unit => 'm', value => 60 }, { unit => 's', value => 1 }, ]; my %values = ('y' => 1, 'M' => 2, 'w' => 3, 'd' => 4, 'h' => 5, 'm' => 6, 's' => 7); my $sign = ''; if ($options{value} < 0) { $sign = '-'; $options{value} = abs($options{value}); } foreach (@$periods) { next if (defined($options{start}) && $values{$_->{unit}} < $values{$options{start}}); my $count = int($options{value} / $_->{value}); next if ($count == 0); $str .= $str_append . $count . $_->{unit}; $options{value} = $options{value} % $_->{value}; $str_append = ' '; } if ($str eq '') { $str = $options{value}; $str .= $options{start} if (defined($options{start})); } return $sign . $str; } sub scale_bytesbit { my (%options) = @_; my $base = 1024; if (defined($options{dst_unit}) && defined($options{src_unit})) { $options{value} *= 8 if ($options{dst_unit} =~ /b/ && $options{src_unit} =~ /B/); $options{value} /= 8 if ($options{dst_unit} =~ /B/ && $options{src_unit} =~ /b/); if ($options{dst_unit} =~ /b/) { $base = 1000; } } my %expo = ('' => 0, k => 1, m => 2, g => 3, t => 4, p => 5, e => 6); my ($src_expo, $dst_expo) = (0, 0); $src_expo = $expo{lc($options{src_quantity})} if (defined($options{src_quantity}) && $options{src_quantity} =~ /[kmgtpe]/i); if ($options{dst_unit} eq 'auto') { my @auto = ('', 'k', 'm', 'g', 't', 'p', 'e'); my $i = defined($options{src_quantity}) ? $expo{$options{src_quantity}} : 0; for (; $i < scalar(@auto); $i++) { last if ($options{value} < $base); $options{value} = $options{value} / $base; } return ($options{value}, $auto[$i], $options{src_unit}); } elsif (defined($options{dst_quantity}) && ($options{dst_quantity} eq '' || $options{dst_quantity} =~ /[kmgtpe]/i )) { my $dst_expo = $expo{lc($options{dst_quantity})}; if ($dst_expo - $src_expo > 0) { $options{value} = $options{value} / ($base ** ($dst_expo - $src_expo)); } elsif ($dst_expo - $src_expo < 0) { $options{value} = $options{value} * ($base ** (($dst_expo - $src_expo) * -1)); } } return $options{value}; } sub convert_bytes { my (%options) = @_; my %expo = (k => 1, m => 2, g => 3, t => 4, p => 5); my ($value, $unit) = ($options{value}, $options{unit}); if (defined($options{pattern})) { return undef if ($value !~ /$options{pattern}/); $value = $1; $unit = $2; } my $base = defined($options{network}) ? 1000 : 1024; if ($unit =~ /([kmgtp])i?b/i) { $value = $value * ($base ** $expo{lc($1)}); } return $value; } sub convert_fahrenheit { my (%options) = @_; return ($options{value} - 32) / 1.8; } sub expand_exponential { my (%options) = @_; return $options{value} unless ($options{value} =~ /^(.*)e([-+]?)(.*)$/); my ($num, $sign, $exp) = ($1, $2, $3); my $sig = $sign eq '-' ? "." . ($exp - 1 + length $num) : ''; return sprintf("%${sig}f", $options{value}); } sub alert_triggered { my (%options) = @_; my ($rv_warn, $warning) = parse_threshold(threshold => $options{warning}); my ($rv_crit, $critical) = parse_threshold(threshold => $options{critical}); foreach ([$rv_warn, $warning], [$rv_crit, $critical]) { next if ($_->[0] == 0); if ($_->[1]->{arobase} == 0 && ($options{value} < $_->[1]->{start} || $options{value} > $_->[1]->{end})) { return 1; } elsif ($_->[1]->{arobase} == 1 && ($options{value} >= $_->[1]->{start} && $options{value} <= $_->[1]->{end})) { return 1; } } return 0; } sub parse_threshold { my (%options) = @_; my $perf = trim($options{threshold}); my $perf_result = { arobase => 0, infinite_neg => 0, infinite_pos => 0, start => '', end => '' }; my $global_status = 1; if ($perf =~ /^(\@?)((?:~|(?:\+|-)?\d+(?:[\.,]\d+)?(?:[KMGTPE][bB])?|):)?((?:\+|-)?\d+(?:[\.,]\d+)?(?:[KMGTPE][bB])?)?$/) { $perf_result->{start} = $2 if (defined($2)); $perf_result->{end} = $3 if (defined($3)); $perf_result->{arobase} = 1 if (defined($1) && $1 eq '@'); $perf_result->{start} =~ s/[\+:]//g; $perf_result->{end} =~ s/\+//; if ($perf_result->{start} =~ s/([KMGTPE])([bB])//) { $perf_result->{start} = scale_bytesbit( value => $perf_result->{start}, src_unit => $2, dst_unit => $2, src_quantity => $1, dst_quantity => '', ); } if ($perf_result->{end} =~ s/([KMGTPE])([bB])//) { $perf_result->{end} = scale_bytesbit( value => $perf_result->{end}, src_unit => $2, dst_unit => $2, src_quantity => $1, dst_quantity => '', ); } if ($perf_result->{end} eq '') { $perf_result->{end} = 1e500; $perf_result->{infinite_pos} = 1; } $perf_result->{start} = 0 if ($perf_result->{start} eq ''); $perf_result->{start} =~ s/,/\./; $perf_result->{end} =~ s/,/\./; if ($perf_result->{start} eq '~') { $perf_result->{start} = -1e500; $perf_result->{infinite_neg} = 1; } } else { $global_status = 0; } return ($global_status, $perf_result); } sub get_threshold_litteral { my (%options) = @_; my $perf_output = ($options{arobase} == 1 ? '@' : '') . (($options{infinite_neg} == 0) ? $options{start} : '~') . ':' . (($options{infinite_pos} == 0) ? $options{end} : ''); return $perf_output; } sub set_timezone { my (%options) = @_; return {} if (!defined($options{name}) || $options{name} eq ''); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'DateTime::TimeZone', error_msg => "Cannot load module 'DateTime::TimeZone'." ); if (DateTime::TimeZone->is_valid_name($options{name})) { return { time_zone => DateTime::TimeZone->new(name => $options{name}) }; } # try to manage syntax (:Pacific/Noumea for example) if ($options{name} =~ /^:(.*)$/ && DateTime::TimeZone->is_valid_name($1)) { return { time_zone => DateTime::TimeZone->new(name => $1) }; } return {}; } sub uniq { my %seen; return grep { !$seen{$_}++ } @_; } sub eval_ssl_options { my (%options) = @_; my $ssl_context = {}; return $ssl_context if (!defined($options{ssl_opt})); my ($rv) = centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'Safe', no_quit => 1 ); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'IO::Socket::SSL', no_quit => 1 ); my $safe; if ($rv == 0) { $safe = Safe->new(); $safe->permit_only(':base_core', 'rv2gv', 'padany'); $safe->share('$values'); $safe->share('$assign_var'); $safe->share_from('IO::Socket::SSL', [ 'SSL_VERIFY_NONE', 'SSL_VERIFY_PEER', 'SSL_VERIFY_FAIL_IF_NO_PEER_CERT', 'SSL_VERIFY_CLIENT_ONCE', 'SSL_RECEIVED_SHUTDOWN', 'SSL_SENT_SHUTDOWN', 'SSL_OCSP_NO_STAPLE', 'SSL_OCSP_MUST_STAPLE', 'SSL_OCSP_FAIL_HARD', 'SSL_OCSP_FULL_CHAIN', 'SSL_OCSP_TRY_STAPLE' ]); } foreach (@{$options{ssl_opt}}) { if (/(SSL_[A-Za-z_]+)\s+=>\s*(\S+)/) { my ($label, $eval) = ($1, $2); our $assign_var; if (defined($safe)) { $safe->reval("\$assign_var = $eval", 1); if ($@) { die 'Unsafe code evaluation: ' . $@; } } else { eval "\$assign_var = $eval"; } $ssl_context->{$label} = $assign_var; } } return $ssl_context; } sub slurp_file { my (%options) = @_; my $content = do { local $/ = undef; if (!open my $fh, '<', $options{file}) { $options{output}->add_option_msg(short_msg => "Could not open file $options{file}: $!"); $options{output}->option_exit(); } <$fh>; }; return $content; } sub sanitize_command_param { my (%options) = @_; return if (!defined($options{value})); $options{value} =~ s/[`;!&|]//g; return $options{value}; } my $security_file = '/etc/centreon-plugins/security.json'; my $whitelist_file = '/etc/centreon-plugins/whitelist.json'; if ($^O eq 'MSWin32') { $security_file = 'C:/Program Files/centreon-plugins/security.json'; $whitelist_file = 'C:/Program Files/centreon-plugins/whitelist.json'; } sub check_security_command { my (%options) = @_; return 0 if (!( (defined($options{command}) && $options{command} ne '') || (defined($options{command_options}) && $options{command_options} ne '') || (defined($options{command_path}) && $options{command_path} ne '')) ); return 0 if (! -r "$security_file" || -z "$security_file"); my $content = slurp_file(output => $options{output}, file => $security_file); my $security; eval { $security = JSON::XS->new->utf8->decode($content); }; if ($@) { $options{output}->add_option_msg(short_msg => 'Cannot decode security file content'); $options{output}->option_exit(); } if (defined($security->{block_command_overload}) && $security->{block_command_overload} == 1) { $options{output}->add_option_msg(short_msg => 'Cannot overload command (security)'); $options{output}->option_exit(); } return 0; } sub check_security_whitelist { my (%options) = @_; my $command = $options{command}; $command = $options{command_path} . '/' . $options{command} if (defined($options{command_path}) && $options{command_path} ne ''); $command .= ' ' . $options{command_options} if (defined($options{command_options}) && $options{command_options} ne ''); return 0 if (! -r "$security_file" || -z "$security_file"); my $content = slurp_file(output => $options{output}, file => $security_file); my $security; eval { $security = JSON::XS->new->utf8->decode($content); }; if ($@) { $options{output}->add_option_msg(short_msg => 'Cannot decode security file content'); $options{output}->option_exit(); } return 0 if (!defined($security->{whitelist_enabled}) || $security->{whitelist_enabled} !~ /^(?:1|true)$/i); if (! -r "$whitelist_file") { $options{output}->add_option_msg(short_msg => 'Cannot read whitelist security file content'); $options{output}->option_exit(); } if (-z "$whitelist_file") { $options{output}->add_option_msg(short_msg => 'Cannot execute command (security)'); $options{output}->option_exit(); } $content = slurp_file(output => $options{output}, file => $whitelist_file); my $whitelist; eval { $whitelist = JSON::XS->new->utf8->decode($content); }; if ($@) { $options{output}->add_option_msg(short_msg => 'Cannot decode whitelist security file content'); $options{output}->option_exit(); } my $matched = 0; foreach (@$whitelist) { if ($command =~ /$_/) { $matched = 1; last; } } if ($matched == 0) { $options{output}->add_option_msg(short_msg => 'Cannot execute command (security)'); $options{output}->option_exit(); } return 0; } 1; CENTREON_PLUGINS_MISC $fatpacked{"centreon/plugins/mode.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_MODE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::mode; use strict; use warnings; use centreon::plugins::perfdata; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; $self->{perfdata} = centreon::plugins::perfdata->new(output => $options{output}); %{$self->{option_results}} = (); @{$self->{option_extras}} = @{$options{options}->{extra_arguments}}; $self->{output} = $options{output}; $self->{output}->use_new_perfdata(value => 1) if (defined($options{force_new_perfdata}) && $options{force_new_perfdata} == 1); $self->{mode} = $options{mode}; $self->{version} = '1.0'; return $self; } sub init { my ($self, %options) = @_; # options{default} = { mode_xxx => { option_name => option_value }, } %{$self->{option_results}} = %{$options{option_results}}; # Manage default value return if (!defined($options{default})); foreach (keys %{$options{default}}) { if ($_ eq $self->{mode}) { foreach my $value (keys %{$options{default}->{$_}}) { if (!defined($self->{option_results}->{$value})) { $self->{option_results}->{$value} = $options{default}->{$_}->{$value}; } } } } } sub version { my ($self, %options) = @_; $self->{output}->add_option_msg(short_msg => "Mode Version: " . $self->{version}); } sub disco_format { my ($self, %options) = @_; } sub disco_show { my ($self, %options) = @_; } 1; CENTREON_PLUGINS_MODE $fatpacked{"centreon/plugins/multi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_MULTI'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::multi; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'modes-exec:s' => { name => 'modes_exec' }, 'option-mode:s@' => { name => 'option_mode' } }); $self->{options} = $options{options}; return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (!defined($self->{option_results}->{modes_exec})) { $self->{output}->add_option_msg(short_msg => "Need to specify --modes-exec option."); $self->{output}->option_exit(); } $self->{options_mode_extra} = {}; if (defined($self->{option_results}->{option_mode})) { foreach (@{$self->{option_results}->{option_mode}}) { next if (! /^(.+?),(.*)$/); $self->{options_mode_extra}->{$1} = [] if (!defined($self->{options_mode_extra}->{$1})); push @{$self->{options_mode_extra}->{$1}}, $2; } } $self->{modes} = $options{modes}; } sub run { my ($self, %options) = @_; $self->{output}->parameter(attr => 'nodisplay', value => 1); $self->{output}->parameter(attr => 'noexit_die', value => 1); $self->{output}->use_new_perfdata(value => 1); my @modes = split /,/, $self->{option_results}->{modes_exec}; foreach (@modes) { next if (!defined($self->{modes}->{$_})); eval { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => $self->{modes}->{$_}, error_msg => "Cannot load module --mode $_" ); @ARGV = (@{$self->{options_mode_extra}->{$_}}) if (defined($self->{options_mode_extra}->{$_})); $self->{output}->mode(name => $_); my $mode = $self->{modes}->{$_}->new(options => $self->{options}, output => $self->{output}, mode => $_); $self->{options}->parse_options(); my $option_results = $self->{options}->get_options(); $mode->check_options(option_results => $option_results, %options); $mode->run(%options); }; if ($@) { $self->{output}->output_add(long_msg => 'eval result mode ' . $_ . ': ' . $@, debug => 1); } } $self->{output}->mode(name => 'multi'); $self->{output}->parameter(attr => 'nodisplay', value => 0); $self->{output}->parameter(attr => 'noexit_die', value => 0); $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check multiple modes at once. =over 8 =item B<--modes-exec> Modes to use, separated by commas. Example for linux: --modes-exec=cpu,memory,storage,interfaces =item B<--option-mode> Define options for the modes selected in --modes-exec. The option can be used several times. E.g.: to define two options for the interfaces mode and one for the storage mode: --option-mode='interfaces,--statefile-dir=/tmp' --option-mode='interfaces,--add-traffic' --option-mode='storage,--statefile-dir=/tmp' =back =cut CENTREON_PLUGINS_MULTI $fatpacked{"centreon/plugins/options.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_OPTIONS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::options; use Pod::Usage; use strict; use warnings; my $alternative = 1; sub new { my ($class) = @_; my $self = {}; bless $self, $class; $self->{pod_where_loaded} = 0; $self->{sanity} = 0; $self->{options_stored} = {}; $self->{options} = {}; @{$self->{pod_package}} = (); $self->{pod_packages_once} = {}; $self->{extra_arguments} = []; if ($alternative == 0) { require Getopt::Long; Getopt::Long->import(); Getopt::Long::Configure("pass_through"); Getopt::Long::Configure('bundling'); Getopt::Long::Configure('no_auto_abbrev'); } else { require centreon::plugins::alternative::Getopt; $centreon::plugins::alternative::Getopt::warn_message = 0; centreon::plugins::alternative::Getopt->import(); } return $self; } sub set_sanity { my ($self, %options) = @_; if ($alternative == 0) { Getopt::Long::Configure('no_pass_through'); } else { $centreon::plugins::alternative::Getopt::warn_message = 1; } $self->{sanity} = 1; } sub set_output { my ($self, %options) = @_; $self->{output} = $options{output}; } sub display_help { my ($self, %options) = @_; my $stdout; foreach (@{$self->{pod_package}}) { my $where = $self->pod_where(package => $_->{package}); { local *STDOUT; open STDOUT, '>', \$stdout; pod2usage( -exitval => 'NOEXIT', -input => $where, -verbose => 99, -sections => $_->{sections} ) if (defined($where)); } $self->{output}->add_option_msg(long_msg => $stdout) if (defined($stdout)); } } sub add_help { my ($self, %options) = @_; # $options{package} = string package # $options{sections} = string sections # $options{help_first} = put at the beginning # $options{once} = put help only one time for a package if (defined($options{once}) && defined($self->{pod_packages_once}->{$options{package}})) { return ; } if (defined($options{help_first})) { unshift @{$self->{pod_package}}, {package => $options{package}, sections => $options{sections}}; } else { push @{$self->{pod_package}}, { package => $options{package}, sections => $options{sections} }; } $self->{pod_packages_once}->{$options{package}} = 1; } sub add_options { my ($self, %options) = @_; # $options{arguments} = ref to hash table with string and name to store (example: { 'mode:s' => { name => 'mode', default => 'defaultvalue' ) foreach (keys %{$options{arguments}}) { if (defined($options{arguments}->{$_}->{redirect})) { $self->{options}->{$_} = \$self->{options_stored}->{$options{arguments}->{$_}->{redirect}}; next; } if (defined($options{arguments}->{$_}->{default})) { $self->{options_stored}->{$options{arguments}->{$_}->{name}} = $options{arguments}->{$_}->{default}; } else { $self->{options_stored}->{$options{arguments}->{$_}->{name}} = undef; } $self->{options}->{$_} = \$self->{options_stored}->{$options{arguments}->{$_}->{name}}; } } sub parse_options { my $self = shift; #%{$self->{options_stored}} = (); my $save_warn_handler; if ($self->{sanity} == 1) { $save_warn_handler = $SIG{__WARN__}; $SIG{__WARN__} = sub { $self->{output}->add_option_msg(short_msg => $_[0]); $self->{output}->option_exit(nolabel => 1); }; } # Store all arguments placed after the special argument "--" in the 'extra_arguments' list $self->{options}->{'_double_dash_'} = \$self->{extra_arguments}; GetOptions( %{$self->{options}} ); %{$self->{options}} = (); $SIG{__WARN__} = $save_warn_handler if ($self->{sanity} == 1); } sub pod_where { my ($self, %options) = @_; if ($self->{pod_where_loaded} == 0) { $self->{pod_where_loaded} = 1; my ($code) = centreon::plugins::misc::mymodule_load( module => 'Pod::Find', no_quit => 1 ); if ($code) { $code = centreon::plugins::misc::mymodule_load( module => 'Pod::Simple::Search', no_quit => 1 ); die "Cannot load module 'Pod::Simple::Search'" if ($code); $self->{pod_where_loaded} = 2; $self->{pod_simple_search} = Pod::Simple::Search->new(); $self->{pod_simple_search}->inc(1); } } if ($self->{pod_where_loaded} == 1) { return Pod::Find::pod_where({-inc => 1}, $options{package}); } return $self->{pod_simple_search}->find($options{package}); } sub get_option { my ($self, %options) = @_; return $self->{options_stored}->{$options{argument}}; } sub get_options { my $self = shift; return $self->{options_stored}; } sub clean { my $self = shift; $self->{options_stored} = {}; } 1; CENTREON_PLUGINS_OPTIONS $fatpacked{"centreon/plugins/output.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_OUTPUT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::output; use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{options})) { print "Class Output: Need to specify 'options' argument to load.\n"; exit 3; } $options{options}->add_options(arguments => { 'explode-perfdata-max:s@' => { name => 'explode_perfdata_max' }, 'range-perfdata:s' => { name => 'range_perfdata' }, 'filter-perfdata:s' => { name => 'filter_perfdata' }, 'filter-perfdata-adv:s' => { name => 'filter_perfdata_adv' }, 'change-perfdata:s@' => { name => 'change_perfdata' }, 'extend-perfdata:s@' => { name => 'extend_perfdata' }, 'extend-perfdata-group:s@'=> { name => 'extend_perfdata_group' }, 'change-exit:s@' => { name => 'change_exit' }, 'change-short-output:s@' => { name => 'change_short_output' }, 'change-long-output:s@' => { name => 'change_long_output' }, 'use-new-perfdata' => { name => 'use_new_perfdata' }, 'filter-uom:s' => { name => 'filter_uom' }, 'verbose' => { name => 'verbose' }, 'debug' => { name => 'debug' }, 'debug-stream' => { name => 'debug_stream' }, 'opt-exit:s' => { name => 'opt_exit', default => 'unknown' }, 'output-xml' => { name => 'output_xml' }, 'output-json' => { name => 'output_json' }, 'output-ignore-perfdata' => { name => 'output_ignore_perfdata' }, 'output-ignore-label' => { name => 'output_ignore_label' }, 'output-openmetrics' => { name => 'output_openmetrics' }, 'output-file:s' => { name => 'output_file' }, 'disco-format' => { name => 'disco_format' }, 'disco-show' => { name => 'disco_show' }, 'float-precision:s' => { name => 'float_precision', default => 8 }, 'source-encoding:s' => { name => 'source_encoding' , default => 'UTF-8' } }); $self->{option_results} = {}; $self->{option_msg} = []; $self->{nodisplay} = 0; $self->{noexit_die} = 0; $self->{is_output_xml} = 0; $self->{is_output_json} = 0; $self->{errors} = {OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3, PENDING => 4}; $self->{errors_num} = {0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN', 4 => 'PENDING'}; $self->{myerrors} = {0 => "OK", 1 => "WARNING", 3 => "UNKNOWN", 7 => "CRITICAL"}; $self->{myerrors_mask} = {CRITICAL => 7, WARNING => 1, UNKNOWN => 3, OK => 0}; $self->{global_short_concat_outputs} = {OK => undef, WARNING => undef, CRITICAL => undef, UNKNOWN => undef, UNQUALIFIED_YET => undef}; $self->{global_short_outputs} = {OK => [], WARNING => [], CRITICAL => [], UNKNOWN => [], UNQUALIFIED_YET => []}; $self->{global_long_output} = []; $self->{perfdatas} = []; $self->{explode_perfdatas} = {}; $self->{change_perfdata} = {}; $self->{explode_perfdata_total} = 0; $self->{range_perfdata} = 0; $self->{global_status} = 0; $self->{encode_import} = 0; $self->{perlqq} = 0; $self->{safe_test} = 0; $self->{disco_elements} = []; $self->{disco_entries} = []; $self->{plugin} = ''; $self->{mode} = ''; return $self; } sub check_options { my ($self, %options) = @_; # $options{option_results} = ref to options result %{$self->{option_results}} = %{$options{option_results}}; $self->{option_results}->{opt_exit} = lc($self->{option_results}->{opt_exit}); if (!$self->is_litteral_status(status => $self->{option_results}->{opt_exit})) { $self->add_option_msg(short_msg => "Unknown value '" . $self->{option_results}->{opt_exit} . "' for --opt-exit."); $self->option_exit(exit_litteral => 'unknown'); } # Go in XML Mode if ($self->is_disco_show() || $self->is_disco_format()) { # By Default XML if (!defined($self->{option_results}->{output_json})) { $self->{option_results}->{output_xml} = 1; } } if (defined($self->{option_results}->{range_perfdata})) { $self->{range_perfdata} = $self->{option_results}->{range_perfdata}; $self->{range_perfdata} = 1 if ($self->{range_perfdata} eq ''); if ($self->{range_perfdata} !~ /^[012]$/) { $self->add_option_msg(short_msg => "Wrong range-perfdata option '" . $self->{range_perfdata} . "'"); $self->option_exit(); } } if (defined($self->{option_results}->{change_exit})) { $self->{change_exit} = {}; foreach (@{$self->{option_results}->{change_exit}}) { my ($src, $dst) = split(/=/); next if (!defined($src) || !defined($self->{errors}->{ uc($src) })); next if (!defined($dst) || !defined($self->{errors}->{ uc($dst) })); $self->{change_exit}->{uc($src)} = uc($dst); } } if (defined($self->{option_results}->{explode_perfdata_max})) { if (${$self->{option_results}->{explode_perfdata_max}}[0] eq '') { $self->{explode_perfdata_total} = 2; } else { $self->{explode_perfdata_total} = 1; foreach (@{$self->{option_results}->{explode_perfdata_max}}) { my ($perf_match, $perf_result) = split /,/; if (!defined($perf_result)) { $self->add_option_msg(short_msg => "Wrong explode-perfdata-max option '" . $_ . "' (syntax: match,value)"); $self->option_exit(); } $self->{explode_perfdatas}->{$perf_match} = $perf_result; } } } if (defined($self->{option_results}->{filter_perfdata_adv}) && $self->{option_results}->{filter_perfdata_adv} ne '') { $self->{option_results}->{filter_perfdata_adv} =~ s/%\{(.*?)\}/\$values->{$1}/g; $self->{option_results}->{filter_perfdata_adv} =~ s/%\((.*?)\)/\$values->{$1}/g; $self->{option_results}->{filter_perfdata_adv} =~ s/alert_triggered\(\)/alert_triggered\(%\$values\)/g; } $self->load_perfdata_extend_args(); $self->{option_results}->{use_new_perfdata} = 1 if (defined($self->{option_results}->{output_openmetrics})); $self->{source_encoding} = (!defined($self->{option_results}->{source_encoding}) || $self->{option_results}->{source_encoding} eq '') ? 'UTF-8' : $self->{option_results}->{source_encoding}; } sub add_option_msg { my ($self, %options) = @_; # $options{short_msg} = string msg # $options{long_msg} = string msg $options{severity} = 'UNQUALIFIED_YET'; $self->output_add(%options); } sub set_ignore_label { my ($self, %options) = @_; $self->{option_results}->{output_ignore_label} = 1; } sub set_status { my ($self, %options) = @_; # $options{exit_litteral} = string litteral exit # Nothing to do for 'UNQUALIFIED_YET' if (!$self->{myerrors_mask}->{uc($options{exit_litteral})}) { return ; } $self->{global_status} |= $self->{myerrors_mask}->{uc($options{exit_litteral})}; } sub output_add { my ($self, %params) = @_; my %args = ( severity => 'OK', separator => ' - ', debug => 0, short_msg => undef, long_msg => undef, ); my $options = { %args, %params }; if (defined($options->{short_msg})) { chomp $options->{short_msg}; if (defined($self->{global_short_concat_outputs}->{uc($options->{severity})})) { $self->{global_short_concat_outputs}->{uc($options->{severity})} .= $options->{separator} . $options->{short_msg}; } else { $self->{global_short_concat_outputs}->{uc($options->{severity})} = $options->{short_msg}; } push @{$self->{global_short_outputs}->{uc($options->{severity})}}, $options->{short_msg}; $self->set_status(exit_litteral => $options->{severity}); } if (defined($options->{long_msg})) { chomp $options->{long_msg}; push @{$self->{global_long_output}}, $options->{long_msg} if ($options->{debug} == 0 || defined($self->{option_results}->{debug})); print $options->{long_msg} . "\n" if (defined($self->{option_results}->{debug_stream})); } } sub perfdata_add { my ($self, %options) = @_; my $perfdata = { label => '', value => '', unit => '', warning => '', critical => '', min => '', max => '', mode => $self->{mode} }; foreach (keys %options) { next if (!defined($options{$_})); $perfdata->{$_} = $options{$_}; } if ((defined($self->{option_results}->{use_new_perfdata}) || defined($options{force_new_perfdata})) && defined($options{nlabel})) { $perfdata->{label} = $options{nlabel}; } if (defined($options{instances})) { $options{instances} = [$options{instances}] if (!ref($options{instances})); my ($external_instance_separator, $internal_instance_separator) = ('#', '~'); if (defined($self->{option_results}->{use_new_perfdata}) || defined($options{force_new_perfdata})) { $perfdata->{label} = join('~', @{$options{instances}}) . '#' . $perfdata->{label}; } else { $perfdata->{label} .= '_' . join('_', @{$options{instances}}); } } $perfdata->{label} =~ s/'/''/g; push @{$self->{perfdatas}}, $perfdata; } sub filter_perfdata { my ($self, %options) = @_; return 1 if ( defined($self->{option_results}->{filter_perfdata}) && $options{perf}->{label} !~ /$self->{option_results}->{filter_perfdata}/ ); return 1 if ( defined($self->{option_results}->{filter_perfdata_adv}) && $self->{option_results}->{filter_perfdata_adv} ne '' && !$self->test_eval(test => $self->{option_results}->{filter_perfdata_adv}, values => $options{perf}) ); return 0; } sub range_perfdata { my ($self, %options) = @_; return if ($self->{range_perfdata} == 0); if ($self->{range_perfdata} == 1) { for (my $i = 0; $i < scalar(@{$options{ranges}}); $i++) { ${${$options{ranges}}[$i]} =~ s/^(@?)-?[0\.]+:/$1/; } } else { for (my $i = 0; $i < scalar(@{$options{ranges}}); $i++) { ${${$options{ranges}}[$i]} = ''; } } } sub output_json { my ($self, %options) = @_; my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0; my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0; my $json_content = { plugin => { name => $self->{plugin}, mode => $self->{mode}, exit => $options{exit_litteral}, outputs => [], perfdatas => [] } }; foreach my $code_litteral (keys %{$self->{global_short_outputs}}) { foreach (@{$self->{global_short_outputs}->{$code_litteral}}) { my ($child_output, $child_type, $child_msg, $child_exit); my $lcode_litteral = ($code_litteral eq 'UNQUALIFIED_YET' ? uc($options{exit_litteral}) : $code_litteral); push @{$json_content->{plugin}->{outputs}}, { type => 1, msg => ($options{nolabel} == 0 ? ($lcode_litteral . ': ') : '') . $_, exit => $lcode_litteral }; } } if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) { foreach (@{$self->{global_long_output}}) { push @{$json_content->{plugin}->{outputs}}, { type => 2, msg => $_ }; } } if ($options{force_ignore_perfdata} == 0) { $self->change_perfdata(); foreach my $perf (@{$self->{perfdatas}}) { next if ($self->filter_perfdata(perf => $perf)); $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]); my %values = (); foreach my $key (keys %$perf) { $perf->{$key} = '' if (defined($self->{option_results}->{filter_uom}) && $key eq 'unit' && $perf->{$key} !~ /$self->{option_results}->{filter_uom}/); $values{$key} = $perf->{$key}; } push @{$json_content->{plugin}->{perfdatas}}, { %values }; } } print $self->{json_output}->encode($json_content); } sub output_xml { my ($self, %options) = @_; my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0; my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0; my ($child_plugin_name, $child_plugin_mode, $child_plugin_exit, $child_plugin_output, $child_plugin_perfdata); my $root = $self->{xml_output}->createElement('plugin'); $self->{xml_output}->setDocumentElement($root); $child_plugin_name = $self->{xml_output}->createElement('name'); $child_plugin_name->appendText($self->{plugin}); $child_plugin_mode = $self->{xml_output}->createElement('mode'); $child_plugin_mode->appendText($self->{mode}); $child_plugin_exit = $self->{xml_output}->createElement('exit'); $child_plugin_exit->appendText($options{exit_litteral}); $child_plugin_output = $self->{xml_output}->createElement('outputs'); $child_plugin_perfdata = $self->{xml_output}->createElement('perfdatas'); $root->addChild($child_plugin_name); $root->addChild($child_plugin_mode); $root->addChild($child_plugin_exit); $root->addChild($child_plugin_output); $root->addChild($child_plugin_perfdata); foreach my $code_litteral (keys %{$self->{global_short_outputs}}) { foreach (@{$self->{global_short_outputs}->{$code_litteral}}) { my ($child_output, $child_type, $child_msg, $child_exit); my $lcode_litteral = ($code_litteral eq 'UNQUALIFIED_YET' ? uc($options{exit_litteral}) : $code_litteral); $child_output = $self->{xml_output}->createElement('output'); $child_plugin_output->addChild($child_output); $child_type = $self->{xml_output}->createElement('type'); $child_type->appendText(1); # short $child_msg = $self->{xml_output}->createElement('msg'); $child_msg->appendText(($options{nolabel} == 0 ? ($lcode_litteral . ': ') : '') . $_); $child_exit = $self->{xml_output}->createElement('exit'); $child_exit->appendText($lcode_litteral); $child_output->addChild($child_type); $child_output->addChild($child_exit); $child_output->addChild($child_msg); } } if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) { foreach (@{$self->{global_long_output}}) { my ($child_output, $child_type, $child_msg); $child_output = $self->{xml_output}->createElement('output'); $child_plugin_output->addChild($child_output); $child_type = $self->{xml_output}->createElement('type'); $child_type->appendText(2); # long $child_msg = $self->{xml_output}->createElement('msg'); $child_msg->appendText($_); $child_output->addChild($child_type); $child_output->addChild($child_msg); } } if ($options{force_ignore_perfdata} == 0) { $self->change_perfdata(); foreach my $perf (@{$self->{perfdatas}}) { next if ($self->filter_perfdata(perf => $perf)); $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]); my ($child_perfdata); $child_perfdata = $self->{xml_output}->createElement('perfdata'); $child_plugin_perfdata->addChild($child_perfdata); foreach my $key (keys %$perf) { $perf->{$key} = '' if (defined($self->{option_results}->{filter_uom}) && $key eq 'unit' && $perf->{$key} !~ /$self->{option_results}->{filter_uom}/); my $child = $self->{xml_output}->createElement($key); $child->appendText($perf->{$key}); $child_perfdata->addChild($child); } } } print $self->{xml_output}->toString(1); } sub output_openmetrics { my ($self, %options) = @_; centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Time::HiRes', error_msg => "Cannot load module 'Time::HiRes'." ); my $time_ms = int(Time::HiRes::time() * 1000); $self->change_perfdata(); foreach my $perf (@{$self->{perfdatas}}) { next if ($self->filter_perfdata(perf => $perf)); $perf->{unit} = '' if ( defined($self->{option_results}->{filter_uom}) && $perf->{unit} !~ /$self->{option_results}->{filter_uom}/ ); $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]); my $label = $perf->{label}; my $instance; if ($label =~ /^(.*?)#(.*)$/) { ($perf->{instance}, $label) = ($1, $2); } my ($bucket, $append) = ('{plugin="' . $self->{plugin} . '",mode="' . $perf->{mode} . '"', ''); foreach ('unit', 'warning', 'critical', 'min', 'max', 'instance') { if (defined($perf->{$_}) && $perf->{$_} ne '') { $bucket .= ',' . $_ . '="' . $perf->{$_} . '"'; } } $bucket .= '}'; print $label . $bucket . ' ' . $perf->{value} . ' ' . $time_ms . "\n"; } } sub output_txt_short_display { my ($self, %options) = @_; if (defined($self->{global_short_concat_outputs}->{CRITICAL})) { print (($options{nolabel} == 0 ? 'CRITICAL: ' : '') . $self->{global_short_concat_outputs}->{CRITICAL} . " "); } if (defined($self->{global_short_concat_outputs}->{WARNING})) { print (($options{nolabel} == 0 ? 'WARNING: ' : '') . $self->{global_short_concat_outputs}->{WARNING} . " "); } if (defined($self->{global_short_concat_outputs}->{UNKNOWN})) { print (($options{nolabel} == 0 ? 'UNKNOWN: ' : '') . $self->{global_short_concat_outputs}->{UNKNOWN} . " "); } if (uc($options{exit_litteral}) eq 'OK') { print (($options{nolabel} == 0 ? 'OK: ' : '') . (defined($self->{global_short_concat_outputs}->{OK}) ? $self->{global_short_concat_outputs}->{OK} : '') . " "); } } sub output_txt_short { my ($self, %options) = @_; if (!defined($self->{option_results}->{change_short_output})) { $self->output_txt_short_display(%options); return ; } my $stdout = ''; { local *STDOUT; open STDOUT, '>', \$stdout; $self->output_txt_short_display(%options); } foreach (@{$self->{option_results}->{change_short_output}}) { my ($pattern, $replace, $modifier) = split /~/; next if (!defined($pattern)); $replace = '' if (!defined($replace)); $modifier = '' if (!defined($modifier)); eval "\$stdout =~ s{$pattern}{$replace}$modifier"; } print $stdout; } sub output_txt { my ($self, %options) = @_; my $force_ignore_perfdata = (defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) ? 1 : 0; my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0; return if ($self->{nodisplay} == 1); if (defined($self->{global_short_concat_outputs}->{UNQUALIFIED_YET})) { $self->output_add(severity => uc($options{exit_litteral}), short_msg => $self->{global_short_concat_outputs}->{UNQUALIFIED_YET}); } $self->output_txt_short(%options); if ($force_ignore_perfdata == 0) { my $pipe = 0; $self->change_perfdata(); foreach my $perf (@{$self->{perfdatas}}) { next if ($self->filter_perfdata(perf => $perf)); $perf->{unit} = '' if (defined($self->{option_results}->{filter_uom}) && $perf->{unit} !~ /$self->{option_results}->{filter_uom}/); $self->range_perfdata(ranges => [\$perf->{warning}, \$perf->{critical}]); if ($pipe == 0) { print '|'; $pipe = 1; } print " '" . $perf->{label} . "'=" . $perf->{value} . $perf->{unit} . ';' . $perf->{warning} . ';' . $perf->{critical} . ';' . $perf->{min} . ';' . $perf->{max}; } } print "\n"; if (defined($self->{option_results}->{verbose}) || $force_long_output == 1) { if (scalar(@{$self->{global_long_output}})) { print join("\n", @{$self->{global_long_output}}); print "\n"; } } } sub change_long_output { my ($self, %options) = @_; return if (!(defined($self->{option_results}->{verbose}) || $options{force_long_output} == 1)); return if (!defined($self->{option_results}->{change_long_output})); my $long_output = join("\n", @{$self->{global_long_output}}); foreach (@{$self->{option_results}->{change_long_output}}) { my ($pattern, $replace, $modifier) = split /~/; next if (!defined($pattern)); $replace = '' if (!defined($replace)); $modifier = '' if (!defined($modifier)); eval "\$long_output =~ s{$pattern}{$replace}$modifier"; } $self->{global_long_output} = [split(/\n/, $long_output)]; } sub display { my ($self, %options) = @_; my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0; my $force_ignore_perfdata = ((defined($options{force_ignore_perfdata}) && $options{force_ignore_perfdata} == 1) || $self->{option_results}->{output_ignore_perfdata}) ? 1 : 0; my $force_long_output = (defined($options{force_long_output}) && $options{force_long_output} == 1) ? 1 : 0; $force_long_output = 1 if (defined($self->{option_results}->{debug})); if (defined($self->{option_results}->{output_openmetrics})) { $self->perfdata_add(nlabel => 'plugin.mode.status', value => $self->{errors}->{$self->{myerrors}->{$self->{global_status}}}); } if (defined($self->{option_results}->{change_long_output})) { $self->change_long_output(force_long_output => $force_long_output); } return if ($self->{nodisplay} == 1); if (defined($self->{option_results}->{output_file})) { if (!open (STDOUT, '>', $self->{option_results}->{output_file})) { $self->output_add( severity => 'UNKNOWN', short_msg => "cannot open file '" . $self->{option_results}->{output_file} . "': $!" ); } } if (defined($self->{option_results}->{output_xml})) { $self->create_xml_document(); if ($self->{is_output_xml}) { $self->output_xml( exit_litteral => $self->get_litteral_status(), nolabel => $nolabel, force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output ); return ; } } elsif (defined($self->{option_results}->{output_json})) { $self->create_json_document(); if ($self->{is_output_json}) { $self->output_json( exit_litteral => $self->get_litteral_status(), nolabel => $nolabel, force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output ); return ; } } elsif (defined($self->{option_results}->{output_openmetrics})) { $self->output_openmetrics(); return ; } $self->output_txt( exit_litteral => $self->get_litteral_status(), nolabel => $nolabel, force_ignore_perfdata => $force_ignore_perfdata, force_long_output => $force_long_output ); } sub die_exit { my ($self, %options) = @_; # $options{exit_litteral} = string litteral exit # $options{nolabel} = interger label display my $exit_litteral = defined($options{exit_litteral}) ? $options{exit_litteral} : $self->{option_results}->{opt_exit}; my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0; # ignore long output in the following case $self->{option_results}->{verbose} = undef; if (defined($self->{option_results}->{output_xml})) { $self->create_xml_document(); if ($self->{is_output_xml}) { $self->output_xml(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1); $self->exit(exit_litteral => $exit_litteral); } } elsif (defined($self->{option_results}->{output_json})) { $self->create_json_document(); if ($self->{is_output_json}) { $self->output_json(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1); $self->exit(exit_litteral => $exit_litteral); } } $self->output_txt(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1); $self->exit(exit_litteral => $exit_litteral); } sub option_exit { my ($self, %options) = @_; # $options{exit_litteral} = string litteral exit # $options{nolabel} = interger label display my $exit_litteral = defined($options{exit_litteral}) ? $options{exit_litteral} : $self->{option_results}->{opt_exit}; my $nolabel = (defined($options{nolabel}) || defined($self->{option_results}->{output_ignore_label})) ? 1 : 0; if (defined($self->{option_results}->{output_xml})) { $self->create_xml_document(); if ($self->{is_output_xml}) { $self->output_xml(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1); $self->exit(exit_litteral => $exit_litteral); } } elsif (defined($self->{option_results}->{output_json})) { $self->create_json_document(); if ($self->{is_output_json}) { $self->output_json(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1); $self->exit(exit_litteral => $exit_litteral); } } elsif (defined($self->{option_results}->{output_openmetrics})) { $self->set_status(exit_litteral => $exit_litteral); $self->output_openmetrics(); $self->exit(exit_litteral => $exit_litteral); } $self->output_txt(exit_litteral => $exit_litteral, nolabel => $nolabel, force_ignore_perfdata => 1, force_long_output => 1); $self->exit(exit_litteral => $exit_litteral); } sub exit { my ($self, %options) = @_; if ($self->{noexit_die} == 1) { die 'exit'; } my $exit; if (defined($options{exit_litteral})) { $exit = uc($options{exit_litteral}); } else { $exit = $self->{myerrors}->{ $self->{global_status} }; } if (defined($self->{change_exit}) && defined($self->{change_exit}->{$exit})) { $exit = $self->{change_exit}->{$exit}; } exit $self->{errors}->{$exit}; } sub get_option { my ($self, %options) = @_; return $self->{option_results}->{$options{option}}; } sub get_most_critical { my ($self, %options) = @_; my $current_status = 0; # For 'OK' foreach (@{$options{status}}) { if ($self->{myerrors_mask}->{uc($_)} > $current_status) { $current_status = $self->{myerrors_mask}->{uc($_)}; } } return $self->{myerrors}->{$current_status}; } sub get_litteral_status { my ($self, %options) = @_; if (defined($options{status})) { if (defined($self->{errors_num}->{$options{status}})) { return $self->{errors_num}->{$options{status}}; } return $options{status}; } else { return $self->{myerrors}->{$self->{global_status}}; } } sub is_status { my ($self, %options) = @_; # $options{value} = string status # $options{litteral} = value is litteral # $options{compare} = string status if (defined($options{litteral})) { my $value = defined($options{value}) ? $options{value} : $self->get_litteral_status(); if (uc($value) eq uc($options{compare})) { return 1; } return 0; } my $value = defined($options{value}) ? $options{value} : $self->{global_status}; my $dec_val = $self->{myerrors_mask}->{$value}; my $lresult = $value & $dec_val; # Need to manage 0 if ($lresult > 0 || ($dec_val == 0 && $value == 0)) { return 1; } return 0; } sub is_litteral_status { my ($self, %options) = @_; # $options{status} = string status if (defined($self->{errors}->{uc($options{status})})) { return 1; } return 0; } sub create_json_document { my ($self) = @_; if (centreon::plugins::misc::mymodule_load( no_quit => 1, module => 'JSON', error_msg => "Cannot load module 'JSON'.") ) { print "Cannot load module 'JSON'\n"; $self->exit(exit_litteral => 'unknown'); } $self->{is_output_json} = 1; $self->{json_output} = JSON->new->utf8(); } sub create_xml_document { my ($self) = @_; if (centreon::plugins::misc::mymodule_load( no_quit => 1, module => 'XML::LibXML', error_msg => "Cannot load module 'XML::LibXML'.") ) { print "Cannot load module 'XML::LibXML'\n"; $self->exit(exit_litteral => 'unknown'); } $self->{is_output_xml} = 1; $self->{xml_output} = XML::LibXML::Document->new('1.0', 'utf-8'); } sub plugin { my ($self, %options) = @_; # $options{name} = string name if (defined($options{name})) { $self->{plugin} = $options{name}; } return $self->{plugin}; } sub mode { my ($self, %options) = @_; if (defined($options{name})) { $self->{mode} = $options{name}; } return $self->{mode}; } sub add_disco_format { my ($self, %options) = @_; push @{$self->{disco_elements}}, @{$options{elements}}; } sub display_disco_format { my ($self, %options) = @_; if (defined($self->{option_results}->{output_xml})) { $self->create_xml_document(); my $root = $self->{xml_output}->createElement('data'); $self->{xml_output}->setDocumentElement($root); foreach (@{$self->{disco_elements}}) { my $child = $self->{xml_output}->createElement("element"); $child->appendText($_); $root->addChild($child); } print $self->{xml_output}->toString(1); } elsif (defined($self->{option_results}->{output_json})) { $self->create_json_document(); my $json_content = {data => [] }; foreach (@{$self->{disco_elements}}) { push @{$json_content->{data}}, $_; } print $self->{json_output}->encode($json_content); } } sub display_disco_show { my ($self, %options) = @_; if (defined($self->{option_results}->{output_xml})) { $self->create_xml_document(); my $root = $self->{xml_output}->createElement('data'); $self->{xml_output}->setDocumentElement($root); foreach (@{$self->{disco_entries}}) { my $child = $self->{xml_output}->createElement('label'); foreach my $key (keys %$_) { $child->setAttribute($key, $_->{$key}); } $root->addChild($child); } print $self->{xml_output}->toString(1); } elsif (defined($self->{option_results}->{output_json})) { $self->create_json_document(); my $json_content = {data => [] }; foreach (@{$self->{disco_entries}}) { my %values = (); foreach my $key (keys %$_) { $values{$key} = $_->{$key}; } push @{$json_content->{data}}, {%values}; } print $self->{json_output}->encode($json_content); } } sub decode { my ($self, $value) = @_; if ($self->{encode_import} == 0) { # Some Perl version dont have the following module (like Perl 5.6.x) my $rv = centreon::plugins::misc::mymodule_load( no_quit => 1, module => 'Encode', error_msg => "Cannot load module 'Encode'." ); return $value if ($rv); $self->{encode_import} = 1; eval '$self->{perlqq} = Encode::PERLQQ'; } return centreon::plugins::misc::trim(Encode::decode($self->{source_encoding}, $value, $self->{perlqq})); } sub parameter { my ($self, %options) = @_; if (defined($options{attr})) { $self->{$options{attr}} = $options{value}; } return $self->{$options{attr}}; } sub add_disco_entry { my ($self, %options) = @_; push @{$self->{disco_entries}}, {%options}; } sub is_disco_format { my ($self) = @_; if (defined($self->{option_results}->{disco_format})) { return 1; } return 0; } sub is_disco_show { my ($self) = @_; if (defined($self->{option_results}->{disco_show})) { return 1; } return 0; } sub is_verbose { my ($self) = @_; if (defined($self->{option_results}->{verbose})) { return 1; } return 0; } sub is_debug { my ($self) = @_; if (defined($self->{option_results}->{debug}) || defined($self->{option_results}->{debug_stream})) { return 1; } return 0; } sub load_eval { my ($self) = @_; my ($code) = centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Safe', no_quit => 1 ); if ($code == 0) { $self->{safe} = Safe->new(); $self->{safe}->share('$values'); $self->{safe}->share('$assign_var'); $self->{safe}->share_from('centreon::plugins::misc', ['alert_triggered']); } $self->{safe_test} = 1; } sub test_eval { my ($self, %options) = @_; $self->load_eval() if ($self->{safe_test} == 0); my $result; if (defined($self->{safe})) { our $values = $options{values}; $result = $self->{safe}->reval($options{test}, 1); if ($@) { die 'Unsafe code evaluation: ' . $@; } } elsif (defined($options{values})) { my $values = $options{values}; { local $SIG{__WARN__} = sub {}; # ignore $result = eval "$options{test}"; if ($@) { die 'Code evaluation error: ' . $@; } } } return $result; } sub open_eval { my ($self, %options) = @_; $self->load_eval() if ($self->{safe_test} == 0); our $values = $options{values}; $self->{safe}->reval("$options{eval}", 1); return $values; } sub assign_eval { my ($self, %options) = @_; $self->load_eval() if ($self->{safe_test} == 0); our $assign_var; if (defined($self->{safe})) { our $values = $options{values}; $self->{safe}->reval("\$assign_var = $options{eval}", 1); if ($@) { die 'Unsafe code evaluation: ' . $@; } } else { my $values = $options{values}; eval "\$assign_var = $options{eval}"; } return $assign_var; } sub use_new_perfdata { my ($self, %options) = @_; $self->{option_results}->{use_new_perfdata} = $options{value} if (defined($options{value})); if (defined($self->{option_results}->{use_new_perfdata})) { return 1; } return 0; } sub get_instance_perfdata_separator { my ($self) = @_; if (defined($self->{option_results}->{use_new_perfdata})) { return '~'; } return '_'; } sub parse_pfdata_scale { my ($self, %options) = @_; # --extend-perfdata=traffic_in,,scale(Mbps),mbps my $args = { unit => 'auto' }; if ($options{args} =~ /^([KMGTPEkmgtpe])?(B|b|bps|Bps|b\/s|auto)$/) { $args->{quantity} = defined($1) ? $1 : ''; $args->{unit} = $2; } elsif ($options{args} ne '') { return 1; } return (0, $args); } sub parse_pfdata_math { my ($self, %options) = @_; # --extend-perfdata=perfx,,math(current + 10 - 100, 1) my $args = { math => undef, apply_threshold => 0 }; my ($math, $apply_threshold) = split /\|/, $options{args}; if ($math =~ /^((?:[\s\.\-\+\*\/0-9\(\)]|current)+)$/) { $args->{math} = $1; } elsif ($options{args} ne '') { return 1; } if (defined($apply_threshold) && $apply_threshold =~ /^\s*(0|1)\s*$/ ) { $args->{apply_threshold} = $1; } return (0, $args); } sub parse_pfdata_eval { my ($self, %options) = @_; # --extend-perfdata=perfx,,eval(%(label) =~ s/a/A/g) my $args = { expr => $options{args} }; $args->{expr} =~ s/%\{(.*?)\}/\$values->{$1}/g; $args->{expr} =~ s/%\((.*?)\)/\$values->{$1}/g; return (0, $args); } sub parse_group_pfdata { my ($self, %options) = @_; $options{args} =~ s/^\s+//; $options{args} =~ s/\s+$//; my $args = { pattern_pf => $options{args} }; return $args; } sub parse_pfdata_min { my ($self, %options) = @_; my $args = $self->parse_group_pfdata(%options); return (0, $args); } sub parse_pfdata_max { my ($self, %options) = @_; my $args = $self->parse_group_pfdata(%options); return (0, $args); } sub parse_pfdata_average { my ($self, %options) = @_; my $args = $self->parse_group_pfdata(%options); return (0, $args); } sub parse_pfdata_sum { my ($self, %options) = @_; my $args = $self->parse_group_pfdata(%options); return (0, $args); } sub apply_pfdata_scale { my ($self, %options) = @_; return if (${$options{perf}}->{unit} !~ /^([KMGTPEkmgtpe])?(B|b|bps|Bps|b\/s)$/); my ($src_quantity, $src_unit) = ($1, $2); my ($value, $dst_quantity, $dst_unit) = centreon::plugins::misc::scale_bytesbit( value => ${$options{perf}}->{value}, src_quantity => $src_quantity, src_unit => $src_unit, dst_quantity => $options{args}->{quantity}, dst_unit => $options{args}->{unit} ); ${$options{perf}}->{value} = sprintf('%.2f', $value); if (defined($dst_unit)) { ${$options{perf}}->{unit} = $dst_quantity . $dst_unit; } else { ${$options{perf}}->{unit} = $options{args}->{quantity} . $options{args}->{unit}; } if (defined(${$options{perf}}->{max}) && ${$options{perf}}->{max} ne '') { ($value) = centreon::plugins::misc::scale_bytesbit(value => ${$options{perf}}->{max}, src_quantity => $src_quantity, src_unit => $src_unit, dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity}, dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit}); ${$options{perf}}->{max} = sprintf('%.2f', $value); } foreach my $threshold ('warning', 'critical') { next if (${$options{perf}}->{$threshold} eq ''); my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold}); next if ($status == 0); if ($result->{start} ne '' && $result->{infinite_neg} == 0) { ($result->{start}) = centreon::plugins::misc::scale_bytesbit(value => $result->{start}, src_quantity => $src_quantity, src_unit => $src_unit, dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity}, dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit}); } if ($result->{end} ne '' && $result->{infinite_pos} == 0) { ($result->{end}) = centreon::plugins::misc::scale_bytesbit(value => $result->{end}, src_quantity => $src_quantity, src_unit => $src_unit, dst_quantity => defined($dst_unit) ? $dst_quantity : $options{args}->{quantity}, dst_unit => defined($dst_unit) ? $dst_unit : $options{args}->{unit}); } ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result); } } sub apply_pfdata_invert { my ($self, %options) = @_; return if (!defined(${$options{perf}}->{max}) || ${$options{perf}}->{max} eq ''); ${$options{perf}}->{value} = ${$options{perf}}->{max} - ${$options{perf}}->{value}; foreach my $threshold ('warning', 'critical') { next if (${$options{perf}}->{$threshold} eq ''); my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold}); next if ($status == 0); my $tmp = { arobase => $result->{arobase}, infinite_pos => 0, infinite_neg => 0, start => $result->{start}, end => $result->{end} }; $tmp->{infinite_neg} = 1 if ($result->{infinite_pos} == 1); $tmp->{infinite_pos} = 1 if ($result->{infinite_neg} == 1); if ($result->{start} ne '' && $result->{infinite_neg} == 0) { $tmp->{end} = ${$options{perf}}->{max} - $result->{start}; } if ($result->{end} ne '' && $result->{infinite_pos} == 0) { $tmp->{start} = ${$options{perf}}->{max} - $result->{end}; } ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$tmp); } } sub apply_pfdata_percent { my ($self, %options) = @_; return if (!defined(${$options{perf}}->{max}) || ${$options{perf}}->{max} eq ''); ${$options{perf}}->{value} = sprintf('%.2f', ${$options{perf}}->{value} * 100 / ${$options{perf}}->{max}); ${$options{perf}}->{unit} = '%'; foreach my $threshold ('warning', 'critical') { next if (${$options{perf}}->{$threshold} eq ''); my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold}); next if ($status == 0); if ($result->{start} ne '' && $result->{infinite_neg} == 0) { $result->{start} = sprintf('%.2f', $result->{start} * 100 / ${$options{perf}}->{max}); } if ($result->{end} ne '' && $result->{infinite_pos} == 0) { $result->{end} = sprintf('%.2f', $result->{end} * 100 / ${$options{perf}}->{max}); } ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result); } ${$options{perf}}->{max} = 100; } sub apply_pfdata_eval { my ($self, %options) = @_; ${$options{perf}} = $self->open_eval(eval => $options{args}->{expr}, values => ${$options{perf}}); } sub apply_pfdata_math { my ($self, %options) = @_; my $math = $options{args}->{math}; $math =~ s/current/\$value/g; my $value = ${$options{perf}}->{value}; eval "\${\$options{perf}}->{value} = $math"; return if ($options{args}->{apply_threshold} == 0); foreach my $threshold ('warning', 'critical') { next if (${$options{perf}}->{$threshold} eq ''); my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => ${$options{perf}}->{$threshold}); next if ($status == 0); if ($result->{start} ne '' && $result->{infinite_neg} == 0) { $value = $result->{start}; eval "\$result->{start} = $math"; } if ($result->{end} ne '' && $result->{infinite_pos} == 0) { $value = $result->{end}; eval "\$result->{end} = $math"; } ${$options{perf}}->{$threshold} = centreon::plugins::misc::get_threshold_litteral(%$result); } ${$options{perf}}->{max} = 100; } sub apply_pfdata_min { my ($self, %options) = @_; my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\""); my $min; for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) { next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/); next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/); $min = $self->{perfdatas}->[$i]->{value} if (!defined($min) || $min > $self->{perfdatas}->[$i]->{value}); } ${$options{perf}}->{value} = $min if (defined($min)); } sub apply_pfdata_max { my ($self, %options) = @_; my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\""); my $max; for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) { next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/); next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/); $max = $self->{perfdatas}->[$i]->{value} if (!defined($max) || $max < $self->{perfdatas}->[$i]->{value}); } ${$options{perf}}->{value} = $max if (defined($max)); } sub apply_pfdata_sum { my ($self, %options) = @_; my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\""); my ($sum, $num) = (0, 0); for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) { next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/); next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/); $sum += $self->{perfdatas}->[$i]->{value}; $num++; } ${$options{perf}}->{value} = $sum if ($num > 0); } sub apply_pfdata_average { my ($self, %options) = @_; my $pattern_pf = $self->assign_eval(eval => "\"$options{args}->{pattern_pf}\""); my ($sum, $num) = (0, 0); for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) { next if ($self->{perfdatas}->[$i]->{label} !~ /$pattern_pf/); next if ($self->{perfdatas}->[$i]->{value} !~ /\d+/); $sum += $self->{perfdatas}->[$i]->{value}; $num++; } ${$options{perf}}->{value} = sprintf("%.2f", ($sum / $num)) if ($num > 0); } sub apply_perfdata_thresholds { my ($self, %options) = @_; foreach (('warning', 'critical')) { next if (!defined($options{$_})); my @thresholds = split(':', $options{$_}, -1); for (my $i = 0; $i < scalar(@thresholds); $i++) { if ($thresholds[$i] =~ /(\d+(?:\.\d+)?)\s*%/) { if (!defined($options{max}) || $options{max} eq '') { $thresholds[$i] = ''; next; } $thresholds[$i] = $1 * $options{max} / 100; } elsif ($thresholds[$i] =~ /(\d+(?:\.\d+)?)/) { $thresholds[$i] = $1; } else { $thresholds[$i] = ''; } } ${$options{perf}}->{$_} = join(':', @thresholds); } } sub load_perfdata_extend_args { my ($self, %options) = @_; foreach ( [$self->{option_results}->{change_perfdata}, 1], [$self->{option_results}->{extend_perfdata}, 2], [$self->{option_results}->{extend_perfdata_group}, 3], ) { next if (!defined($_->[0])); foreach my $arg (@{$_->[0]}) { $self->parse_perfdata_extend_args(arg => $arg, type => $_->[1]); } } } sub parse_perfdata_extend_args { my ($self, %options) = @_; # --extend-perfdata=searchlabel,newlabel,method[,[newuom],[min],[max],[warning],[critical]] my ($pfdata_match, $pfdata_substitute, $method, $uom_sub, $min_sub, $max_sub, $warn_sub, $crit_sub) = split /,/, $options{arg}; return if ((!defined($pfdata_match) || $pfdata_match eq '') && $options{type} != 3); $self->{pfdata_extends} = [] if (!defined($self->{pfdata_extends})); my $pfdata_extends = { pfdata_match => defined($pfdata_match) && $pfdata_match ne '' ? $pfdata_match : undef, pfdata_substitute => defined($pfdata_substitute) && $pfdata_substitute ne '' ? $pfdata_substitute : undef, uom_sub => defined($uom_sub) && $uom_sub ne '' ? $uom_sub : undef, min_sub => defined($min_sub) && $min_sub ne '' ? $min_sub : undef, max_sub => defined($max_sub) && $max_sub ne '' ? $max_sub : undef, warn_sub => defined($warn_sub) && $warn_sub ne '' ? $warn_sub : undef, crit_sub => defined($crit_sub) && $crit_sub ne '' ? $crit_sub : undef, type => $options{type} }; if (defined($method) && $method ne '') { if ($method !~ /^\s*(invert|percent|scale|math|min|max|average|sum|eval)\s*\(\s*(.*?)\s*\)\s*$/) { $self->output_add(long_msg => "method in argument '$options{arg}' is unknown", debug => 1); return ; } $pfdata_extends->{method_name} = $1; my $args = $2; if (my $func = $self->can('parse_pfdata_' . $pfdata_extends->{method_name})) { (my $status, $pfdata_extends->{method_args}) = $func->($self, args => $args); if ($status == 1) { $self->output_add(long_msg => "argument in method '$options{arg}' is unknown", debug => 1); return ; } } } push @{$self->{pfdata_extends}}, $pfdata_extends; } sub apply_perfdata_explode { my ($self, %options) = @_; return if ($self->{explode_perfdata_total} == 0); foreach (@{$self->{perfdatas}}) { next if ($_->{max} eq ''); if ($self->{explode_perfdata_total} == 2) { $self->perfdata_add(label => $_->{label} . '_max', value => $_->{max}, unit => $_->{unit}); next; } foreach my $regexp (keys %{$self->{explode_perfdatas}}) { if ($_->{label} =~ /$regexp/) { $self->perfdata_add(label => $self->{explode_perfdatas}->{$regexp}, value => $_->{max}, unit => $_->{unit}); last; } } } } sub apply_perfdata_extend { my ($self, %options) = @_; foreach my $extend (@{$self->{pfdata_extends}}) { my $new_pfdata = []; # Manage special case when type group and pfdata_match empty if ($extend->{type} == 3 && (!defined($extend->{pfdata_match}) || $extend->{pfdata_match} eq '')) { next if (!defined($extend->{pfdata_substitute}) || $extend->{pfdata_substitute} eq ''); my $new_perf = { label => $extend->{pfdata_substitute}, value => '', unit => defined($extend->{uom_sub}) ? $extend->{uom_sub} : '', warning => '', critical => '', min => defined($extend->{min_sub}) ? $extend->{min_sub} : '', max => defined($extend->{max_sub}) ? $extend->{max_sub} : '' }; if (defined($extend->{method_name})) { my $func = $self->can('apply_pfdata_' . $extend->{method_name}); $func->($self, perf => \$new_perf, args => $extend->{method_args}); } $self->apply_perfdata_thresholds( perf => \$new_perf, warning => $extend->{warn_sub}, critical => $extend->{crit_sub}, max => $new_perf->{max} ); if (length($new_perf->{value})) { push @{$self->{perfdatas}}, $new_perf; } next; } for (my $i = 0; $i < scalar(@{$self->{perfdatas}}); $i++) { next if ($self->{perfdatas}->[$i]->{label} !~ /$extend->{pfdata_match}/); my $new_perf = { %{$self->{perfdatas}->[$i]} }; if ($extend->{type} == 3) { $new_perf = { label => $self->{perfdatas}->[$i]->{label}, value => '', unit => '', warning => '', critical => '', min => '', max => '' }; } if (defined($extend->{pfdata_substitute})) { eval "\$new_perf->{label} =~ s{$extend->{pfdata_match}}{$extend->{pfdata_substitute}}"; } if (defined($extend->{method_name})) { my $func = $self->can('apply_pfdata_' . $extend->{method_name}); $func->($self, perf => \$new_perf, args => $extend->{method_args}); } $new_perf->{unit} = $extend->{uom_sub} if (defined($extend->{uom_sub})); $new_perf->{min} = $extend->{min_sub} if (defined($extend->{min_sub})); $new_perf->{max} = $extend->{max_sub} if (defined($extend->{max_sub})); $self->apply_perfdata_thresholds( perf => \$new_perf, warning => $extend->{warn_sub}, critical => $extend->{crit_sub}, max => $new_perf->{max} ); if ($extend->{type} == 1) { $self->{perfdatas}->[$i] = $new_perf; } else { push @$new_pfdata, $new_perf if (length($new_perf->{value})); } } push @{$self->{perfdatas}}, @$new_pfdata; } } sub change_perfdata { my ($self, %options) = @_; $self->apply_perfdata_extend(); $self->apply_perfdata_explode(); } 1; =head1 NAME Output class =head1 SYNOPSIS - =head1 OUTPUT OPTIONS =over 8 =item B<--verbose> Display extended status information (long output). =item B<--debug> Display debug messages. =item B<--filter-perfdata> Filter perfdata that match the regexp. Example: adding --filter-perfdata='avg' will remove all metrics that do not contain 'avg' from performance data. =item B<--filter-perfdata-adv> Filter perfdata based on a "if" condition using the following variables: label, value, unit, warning, critical, min, max. Variables must be written either %{variable} or %(variable). Example: adding --filter-perfdata-adv='not (%(value) == 0 and %(max) eq "")' will remove all metrics whose value equals 0 and that don't have a maximum value. =item B<--explode-perfdata-max> Create a new metric for each metric that comes with a maximum limit. The new metric will be named identically with a '_max' suffix). Example: it will split 'used_prct'=26.93%;0:80;0:90;0;100 into 'used_prct'=26.93%;0:80;0:90;0;100 'used_prct_max'=100%;;;; =item B<--change-perfdata> B<--extend-perfdata> Change or extend perfdata. Syntax: --extend-perfdata=searchlabel,newlabel,target[,[newuom],[min],[max]] Common examples: =over 4 Convert storage free perfdata into used: --change-perfdata='free,used,invert()' Convert storage free perfdata into used: --change-perfdata='used,free,invert()' Scale traffic values automatically: --change-perfdata='traffic,,scale(auto)' Scale traffic values in Mbps: --change-perfdata='traffic_in,,scale(Mbps),mbps' Change traffic values in percent: --change-perfdata='traffic_in,,percent()' =back =item B<--extend-perfdata-group> Add new aggregated metrics (min, max, average or sum) for groups of metrics defined by a regex match on the metrics' names. Syntax: --extend-perfdata-group=regex,namesofnewmetrics,calculation[,[newuom],[min],[max]] regex: regular expression namesofnewmetrics: how the new metrics' names are composed (can use $1, $2... for groups defined by () in regex). calculation: how the values of the new metrics should be calculated newuom (optional): unit of measure for the new metrics min (optional): lowest value the metrics can reach max (optional): highest value the metrics can reach Common examples: =over 4 Sum wrong packets from all interfaces (with interface need --units-errors=absolute): --extend-perfdata-group=',packets_wrong,sum(packets_(discard|error)_(in|out))' Sum traffic by interface: --extend-perfdata-group='traffic_in_(.*),traffic_$1,sum(traffic_(in|out)_$1)' =back =item B<--change-short-output> B<--change-long-output> Modify the short/long output that is returned by the plugin. Syntax: --change-short-output=pattern~replacement~modifier Most commonly used modifiers are i (case insensitive) and g (replace all occurrences). Example: adding --change-short-output='OK~Up~gi' will replace all occurrences of 'OK', 'ok', 'Ok' or 'oK' with 'Up' =item B<--change-exit> Replace an exit code with one of your choice. Example: adding --change-exit=unknown=critical will result in a CRITICAL state instead of an UNKNOWN state. =item B<--range-perfdata> Rewrite the ranges displayed in the perfdata. Accepted values: 0: nothing is changed. 1: if the lower value of the range is equal to 0, it is removed. 2: remove the thresholds from the perfdata. =item B<--filter-uom> Mask the units when they don't match the given regular expression. =item B<--opt-exit> Replace the exit code in case of an execution error (i.e. wrong option provided, SSH connection refused, timeout, etc). Default: unknown. =item B<--output-ignore-perfdata> Remove all the metrics from the service. The service will still have a status and an output. =item B<--output-ignore-label> Remove the status label ("OK:", "WARNING:", "UNKNOWN:", CRITICAL:") from the beginning of the output. Example: 'OK: Ram Total:...' will become 'Ram Total:...' =item B<--output-xml> Return the output in XML format (to send to an XML API). =item B<--output-json> Return the output in JSON format (to send to a JSON API). =item B<--output-openmetrics> Return the output in OpenMetrics format (to send to a tool expecting this format). =item B<--output-file> Write output in file (can be combined with json, xml and openmetrics options). E.g.: --output-file=/tmp/output.txt will write the output in /tmp/output.txt. =item B<--disco-format> Applies only to modes beginning with 'list-'. Returns the list of available macros to configure a service discovery rule (formatted in XML). =item B<--disco-show> Applies only to modes beginning with 'list-'. Returns the list of discovered objects (formatted in XML) for service discovery. =item B<--float-precision> Define the float precision for thresholds (default: 8). =item B<--source-encoding> Define the character encoding of the response sent by the monitored resource Default: 'UTF-8'. =head1 DESCRIPTION B<output>. =cut CENTREON_PLUGINS_OUTPUT $fatpacked{"centreon/plugins/passwordmgr/centreonvault.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_CENTREONVAULT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::centreonvault; use strict; use warnings; use Data::Dumper; use centreon::plugins::http; use JSON::XS; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { 'vault-config:s' => { name => 'vault_config', default => '/etc/centreon-engine/centreonvault.json'}, }); $options{options}->add_help(package => __PACKAGE__, sections => 'VAULT OPTIONS'); $self->{output} = $options{output}; $self->{http} = centreon::plugins::http->new(%options, noptions => 1, default_backend => 'curl'); return $self; } sub extract_map_options { my ($self, %options) = @_; $self->{map_option} = []; # Parse all options to find '/\{.*\:\:secret\:\:(.*)\}/' dedicated patern in value and add entries in map_option foreach my $option (keys %{$options{option_results}}) { if (defined($options{option_results}{$option})) { next if ($option eq 'map_option'); if (ref($options{option_results}{$option}) eq 'ARRAY') { foreach (@{$options{option_results}{$option}}) { if ($_ =~ /\{.*\:\:secret\:\:(.*)\:\:(.*)\}/i) { push (@{$self->{request_endpoint}}, "$1::/v1/".$2); push (@{$self->{map_option}}, $option."=%".$_); } } } else { if ($options{option_results}{$option} =~ /\{.*\:\:secret\:\:(.*)\:\:(.*)\}/i) { push (@{$self->{request_endpoint}}, "$1::/v1/".$2); push (@{$self->{map_option}}, $option."=%".$options{option_results}{$option}); } } } } } sub vault_settings { my ($self, %options) = @_; if (!defined($options{option_results}->{vault_config}) || $options{option_results}->{vault_config} eq '') { $self->{output}->add_option_msg(short_msg => "Please set --vault-config option"); $self->{output}->option_exit(); } if (! -f $options{option_results}->{vault_config}) { $self->{output}->add_option_msg(short_msg => "Cannot find file '$options{option_results}->{vault_config}'"); $self->{output}->option_exit(); } my $file_content = do { local $/ = undef; if (!open my $fh, "<", $options{option_results}->{vault_config}) { $self->{output}->add_option_msg(short_msg => "Could not open file $options{option_results}->{vault_config}: $!"); $self->{output}->option_exit(); } <$fh>; }; my $json; eval { $json = JSON::XS->new->utf8->decode($file_content); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode json file"); $self->{output}->option_exit(); } foreach my $vault_name (keys %$json) { $self->{$vault_name}->{vault_protocol} = 'https'; $self->{$vault_name}->{vault_address} = '127.0.0.1'; $self->{$vault_name}->{vault_port} = '8100'; $self->{$vault_name}->{vault_protocol} = $json->{$vault_name}->{'vault-protocol'} if ($json->{$vault_name}->{'vault-protocol'} && $json->{$vault_name}->{'vault-protocol'} ne ''); $self->{$vault_name}->{vault_address} = $json->{$vault_name}->{'vault-address'} if ($json->{$vault_name}->{'vault-address'} && $json->{$vault_name}->{'vault-address'} ne ''); $self->{$vault_name}->{vault_port} = $json->{$vault_name}->{'vault-port'} if ($json->{$vault_name}->{'vault-port'} && $json->{$vault_name}->{'vault-port'} ne ''); $self->{$vault_name}->{vault_token} = $json->{$vault_name}->{'vault-token'} if ($json->{$vault_name}->{'vault-token'} && $json->{$vault_name}->{'vault-token'} ne ''); } } sub request_api { my ($self, %options) = @_; $self->vault_settings(%options); $self->{lookup_values} = {}; foreach my $item (@{$self->{request_endpoint}}) { # Extract vault name configuration from endpoint # 'vault::/v1/<root_path>/monitoring/hosts/7ad55afc-fa9e-4851-85b7-e26f47e421d7' my ($vault_name, $endpoint); if ($item =~ /(.*)\:\:(.*)/i) { $vault_name = $1; $endpoint = $2; } if (!defined($self->{$vault_name})) { $self->{output}->add_option_msg(short_msg => "Cannot get vault access for: $vault_name"); $self->{output}->option_exit(); } my $headers = ['Accept: application/json']; if (defined($self->{$vault_name}->{vault_token})) { push @$headers, 'X-Vault-Token: ' . $self->{$vault_name}->{vault_token}; } my ($response) = $self->{http}->request( hostname => $self->{$vault_name}->{vault_address}, port => $self->{$vault_name}->{vault_port}, proto => $self->{$vault_name}->{vault_protocol}, method => 'GET', url_path => $endpoint, header => $headers ); my $json; eval { $json = JSON::XS->new->utf8->decode($response); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode Vault JSON response: $@"); $self->{output}->option_exit(); }; foreach (keys %{$json->{data}}) { $self->{lookup_values}->{'{' . $_ . '::secret::' . $vault_name . '::' . substr($endpoint, index($endpoint, '/', 1) + 1) . '}'} = $json->{data}->{$_}; } } } sub do_map { my ($self, %options) = @_; foreach (@{$self->{map_option}}) { next if (! /^(.+?)=%(.+)$/); my ($option, $map) = ($1, $2); $map = $self->{lookup_values}->{$2} if (defined($self->{lookup_values}->{$2})); $option =~ s/-/_/g; $options{option_results}->{$option} = $map; } } sub manage_options { my ($self, %options) = @_; $self->extract_map_options(%options); return if (scalar(@{$self->{map_option}}) <= 0); $self->request_api(%options); $self->do_map(%options); } 1; =head1 NAME Centreon Vault password manager =head1 SYNOPSIS Centreon Vault password manager To be used with an array containing keys/values saved in a secret path by resource =head1 VAULT OPTIONS =over 8 =item B<--vault-config> The path to the file defining access to the Centreon vault (/etc/centreon-engine/centreonvault.json by default) =back =head1 DESCRIPTION B<centreonvault>. =cut CENTREON_PLUGINS_PASSWORDMGR_CENTREONVAULT $fatpacked{"centreon/plugins/passwordmgr/environment.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_ENVIRONMENT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::environment; use strict; use warnings; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { 'environment-map-option:s@' => { name => 'environment_map_option' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'ENVIRONMENT OPTIONS'); $self->{output} = $options{output}; return $self; } sub manage_options { my ($self, %options) = @_; return if (!defined($options{option_results}->{environment_map_option})); foreach (@{$options{option_results}->{environment_map_option}}) { next if (! /^(.+?)=(.+)$/); my ($option, $map) = ($1, $2); $option =~ s/-/_/g; $options{option_results}->{$option} = defined($ENV{$map}) ? $ENV{$map} : ''; } } 1; =head1 NAME Environment global =head1 SYNOPSIS environment class =head1 ENVIRONMENT OPTIONS =over 8 =item B<--environment-map-option> Overload plugin option. Example: --environment-map-option="snmp-community=SNMPCOMMUNITY" =back =head1 DESCRIPTION B<environment>. =cut CENTREON_PLUGINS_PASSWORDMGR_ENVIRONMENT $fatpacked{"centreon/plugins/passwordmgr/file.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_FILE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::file; use strict; use warnings; use centreon::plugins::misc; use JSON::Path; use JSON::XS; use Data::Dumper; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { 'secret-file:s' => { name => 'secret_file' }, 'secret-search-value:s@' => { name => 'secret_search_value' }, 'secret-map-option:s@' => { name => 'secret_map_option' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'SECRET FILE OPTIONS'); $self->{output} = $options{output}; $JSON::Path::Safe = 0; return $self; } sub load { my ($self, %options) = @_; if (defined($options{option_results}->{secret_file}) && $options{option_results}->{secret_file} ne '') { if (! -f $options{option_results}->{secret_file} or ! -r $options{option_results}->{secret_file}) { $self->{output}->add_option_msg(short_msg => "Cannot read secret file '$options{option_results}->{secret_file}': $!"); $self->{output}->option_exit(); } my $content = centreon::plugins::misc::slurp_file(output => $self->{output}, file => $options{option_results}->{secret_file}); my $decoded; eval { $decoded = JSON::XS->new->utf8->decode($content); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode secret file"); $self->{output}->option_exit(); } return $decoded; } } sub do_lookup { my ($self, %options) = @_; $self->{lookup_values} = {}; return if (!defined($options{option_results}->{secret_search_value})); foreach (@{$options{option_results}->{secret_search_value}}) { next if (! /^(.+?)=(.+)$/); my ($map, $lookup) = ($1, $2); # Change %{xxx} options usage while ($lookup =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $options{option_results}->{$1} if (defined($options{option_results}->{$1})); $lookup =~ s/\%\{$1\}/$sub/g } my $jpath = JSON::Path->new($lookup); my $result = $jpath->value($options{json}); $self->{output}->output_add(long_msg => 'lookup = ' . $lookup. ' - response = ' . Data::Dumper::Dumper($result), debug => 1); $self->{lookup_values}->{$map} = $result; } } sub do_map { my ($self, %options) = @_; return if (!defined($options{option_results}->{secret_map_option})); foreach (@{$options{option_results}->{secret_map_option}}) { next if (! /^(.+?)=(.+)$/); my ($option, $map) = ($1, $2); # Change %{xxx} options usage while ($map =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $self->{lookup_values}->{$1} if (defined($self->{lookup_values}->{$1})); $map =~ s/\%\{$1\}/$sub/g } $option =~ s/-/_/g; $options{option_results}->{$option} = $map; } } sub manage_options { my ($self, %options) = @_; my $secrets = $self->load(%options); return if (!defined($secrets)); $self->do_lookup(%options, json => $secrets); $self->do_map(%options); } 1; =head1 NAME Secret file global =head1 SYNOPSIS secret file class =head1 SECRET FILE OPTIONS =over 8 =item B<--secret-file> Secret file. =item B<--secret-search-value> Looking for a value in the JSON. Can use JSON Path and other option values. Example: --secret-search-value='password=$..entries.[?($_->{title} =~ /server/i)].password' --secret-search-value='username=$..entries.[?($_->{title} =~ /server/i)].username' --secret-search-value='password=$..entries.[?($_->{title} =~ /%{hostname}/i)].password' =item B<--secret-map-option> Overload plugin option. Example: --secret-map-option="password=%{password}" --secret-map-option="username=%{username}" =back =head1 DESCRIPTION B<secret file>. =cut CENTREON_PLUGINS_PASSWORDMGR_FILE $fatpacked{"centreon/plugins/passwordmgr/hashicorpvault.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_HASHICORPVAULT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::hashicorpvault; use strict; use warnings; use Data::Dumper; use centreon::plugins::http; use Digest::MD5 qw(md5_hex); use JSON::XS; use vars qw($vault_connections); sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { 'auth-method:s' => { name => 'auth_method', default => 'token' }, 'auth-path:s' => { name => 'auth_path' }, 'auth-settings:s%' => { name => 'auth_settings' }, 'map-option:s@' => { name => 'map_option' }, 'secret-path:s@' => { name => 'secret_path' }, 'vault-address:s' => { name => 'vault_address'}, 'vault-port:s' => { name => 'vault_port', default => '8200' }, 'vault-protocol:s' => { name => 'vault_protocol', default => 'http'}, 'vault-token:s' => { name => 'vault_token'} }); $options{options}->add_help(package => __PACKAGE__, sections => 'VAULT OPTIONS'); $self->{output} = $options{output}; $self->{http} = centreon::plugins::http->new(%options, noptions => 1, default_backend => 'curl'); return $self; } sub get_access_token { my ($self, %options) = @_; my $decoded; my $login = $self->parse_auth_method(method => $self->{auth_method}, settings => $self->{auth_settings}); my $post_json = JSON::XS->new->utf8->encode($login); if (!defined($self->{auth_path}) || $self->{auth_path} eq '') { $self->{auth_path} = $self->{auth_method}; } my $url_path = '/v1/auth/'. $self->{auth_path} . '/login/'; $url_path .= $self->{auth_settings}->{username} if (defined($self->{auth_settings}->{username}) && $self->{auth_method} =~ 'userpass|login') ; my $content = $self->{http}->request( hostname => $self->{vault_address}, port => $self->{vault_port}, proto => $self->{vault_protocol}, method => 'POST', header => ['Content-type: application/json'], query_form_post => $post_json, url_path => $url_path ); if (!defined($content) || $content eq '') { $self->{output}->add_option_msg(short_msg => "Authentication endpoint returns empty content [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); $self->{output}->option_exit(); } eval { $decoded = JSON::XS->new->utf8->decode($content); }; if ($@) { $self->{output}->output_add(long_msg => $content, debug => 1); $self->{output}->add_option_msg(short_msg => "Cannot decode response (add --debug option to display returned content)"); $self->{output}->option_exit(); } if (defined($decoded->{errors}[0])) { $self->{output}->output_add(long_msg => "Error message : " . $decoded->{errors}[0], debug => 1); $self->{output}->add_option_msg(short_msg => "Authentication endpoint returns error code '" . $decoded->{errors}[0] . "' (add --debug option for detailed message)"); $self->{output}->option_exit(); } my $access_token = $decoded->{auth}->{client_token}; return $access_token; } sub parse_auth_method { my ($self, %options) = @_; my $login_settings; my $settings_mapping = { azure => [ 'role', 'jwt' ], cert => [ 'name' ], github => [ 'token' ], ldap => [ 'username', 'password' ], okta => [ 'username', 'password', 'totp' ], radius => [ 'username', 'password' ], userpass => [ 'username', 'password' ] }; foreach (@{$settings_mapping->{$options{method}}}) { if (!defined($options{settings}->{$_})) { $self->{output}->add_option_msg(short_msg => 'Missing authentication setting: ' . $_); $self->{output}->option_exit(); } $login_settings->{$_} = $options{settings}->{$_}; }; return $login_settings; } sub settings { my ($self, %options) = @_; if (!defined($options{option_results}->{vault_address}) || $options{option_results}->{vault_address} eq '') { $self->{output}->add_option_msg(short_msg => "Please set the --vault-address option"); $self->{output}->option_exit(); } if ($options{option_results}->{auth_method} eq 'token' && (!defined($options{option_results}->{vault_token}) || $options{option_results}->{vault_token} eq '')) { $self->{output}->add_option_msg(short_msg => "Please set the --vault-token option"); $self->{output}->option_exit(); } if (!defined($options{option_results}->{secret_path}) || $options{option_results}->{secret_path} eq '') { $self->{output}->add_option_msg(short_msg => "Please set the --secret-path option"); $self->{output}->option_exit(); } if (defined($options{option_results}->{auth_path})) { $self->{auth_path} = lc($options{option_results}->{auth_path}); } $self->{auth_method} = lc($options{option_results}->{auth_method}); $self->{auth_settings} = defined($options{option_results}->{auth_settings}) && $options{option_results}->{auth_settings} ne '' ? $options{option_results}->{auth_settings} : {}; $self->{vault_address} = $options{option_results}->{vault_address}; $self->{vault_port} = $options{option_results}->{vault_port}; $self->{vault_protocol} = $options{option_results}->{vault_protocol}; $self->{vault_token} = $options{option_results}->{vault_token}; if (lc($self->{auth_method}) !~ m/azure|cert|github|ldap|okta|radius|userpass|token/ ) { $self->{output}->add_option_msg(short_msg => "Incorrect or unsupported authentication method set in --auth-method"); $self->{output}->option_exit(); } foreach (@{$options{option_results}->{secret_path}}) { $self->{request_endpoint}->{$_} = '/v1/' . $_; } if (defined($options{option_results}->{auth_method}) && $options{option_results}->{auth_method} ne 'token') { $self->{vault_token} = $self->get_access_token(%options); }; $self->{http}->add_header(key => 'Accept', value => 'application/json'); if (defined($self->{vault_token})) { $self->{http}->add_header(key => 'X-Vault-Token', value => $self->{vault_token}); } } sub request_api { my ($self, %options) = @_; $self->settings(%options); my ($raw_data, $raw_response); foreach my $endpoint (keys %{$self->{request_endpoint}}) { my $json; my $response = $self->{http}->request( hostname => $self->{vault_address}, port => $self->{vault_port}, proto => $self->{vault_protocol}, method => 'GET', url_path => $self->{request_endpoint}->{$endpoint} ); $self->{output}->output_add(long_msg => $response, debug => 1); eval { $json = JSON::XS->new->utf8->decode($response); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode Vault JSON response: $@"); $self->{output}->option_exit(); } if ((defined($json->{data}->{metadata}->{deletion_time}) && $json->{data}->{metadata}->{deletion_time} ne '') || $json->{data}->{metadata}->{destroyed} eq 'true') { $self->{output}->add_option_msg(short_msg => "This secret is not valid anymore"); $self->{output}->option_exit(); } foreach (keys %{$json->{data}->{data}}) { $self->{lookup_values}->{'key_' . $endpoint} = $_; $self->{lookup_values}->{'value_' . $endpoint} = $json->{data}->{data}->{$_}; } push(@{$raw_data}, $json); push(@{$raw_response}, $response); } return ($raw_data, $raw_response); } sub do_map { my ($self, %options) = @_; return if (!defined($options{option_results}->{map_option})); foreach (@{$options{option_results}->{map_option}}) { next if (! /^(.+?)=(.+)$/); my ($option, $map) = ($1, $2); # Change %{xxx} options usage while ($map =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $self->{lookup_values}->{$1} if (defined($self->{lookup_values}->{$1})); $map =~ s/\%\{$1\}/$sub/g; } $option =~ s/-/_/g; $options{option_results}->{$option} = $map; } } sub manage_options { my ($self, %options) = @_; my ($content, $debug) = $self->request_api(%options); if (!defined($content)) { $self->{output}->add_option_msg(short_msg => "Cannot read Vault information"); $self->{output}->option_exit(); } $self->do_map(%options); $self->{output}->output_add(long_msg => Data::Dumper::Dumper($debug), debug => 1) if ($self->{output}->is_debug()); } 1; =head1 NAME HashiCorp Vault global =head1 SYNOPSIS HashiCorp Vault class To be used with K/V engines =head1 VAULT OPTIONS =over 8 =item B<--vault-address> IP address of the HashiCorp Vault server (mandatory). =item B<--vault-port> Port of the HashiCorp Vault server (default: '8200'). =item B<--vault-protocol> HTTP of the HashiCorp Vault server. Can be: 'http', 'https' (default: http). =item B<--auth-method> Authentication method to log in against the Vault server. Can be: 'azure', 'cert', 'github', 'ldap', 'okta', 'radius', 'userpass' (default: 'token'); =item B<--auth-path> Authentication path for 'userpass'. Is an optional setting. More information here: https://developer.hashicorp.com/vault/docs/auth/userpass#configuration =item B<--vault-token> Directly specify a valid token to log in (only for --auth-method='token'). =item B<--auth-settings> Required information to log in according to the selected method. Examples: for 'userpass': --auth-settings='username=user1' --auth-settings='password=my_password' for 'azure': --auth-settings='role=my_azure_role' --auth-settings='jwt=my_azure_token' More information here: https://www.vaultproject.io/api-docs/auth =item B<--secret-path> Location of the secret in the Vault K/V engine (mandatory - Can be multiple). Examples: for v1 engine: --secret-path='mysecrets/servicecredentials' for v2 engine: --secret-path='mysecrets/data/servicecredentials?version=12' More information here: https://www.vaultproject.io/api-docs/secret/kv =item B<--map-option> Overload Plugin option with K/V values. Use the following syntax: the_option_to_overload='%{key_$secret_path$}' or the_option_to_overload='%{value_$secret_path$}' Example: --map-option='username=%{key_mysecrets/servicecredentials}' --map-option='password=%{value_mysecrets/servicecredentials}' =back =head1 DESCRIPTION B<hashicorpvault>. =cut CENTREON_PLUGINS_PASSWORDMGR_HASHICORPVAULT $fatpacked{"centreon/plugins/passwordmgr/keepass.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_KEEPASS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::keepass; use strict; use warnings; use JSON::Path; use Data::Dumper; use KeePass::Reader; use vars qw($keepass_connections); sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { 'keepass-endpoint:s' => { name => 'keepass_endpoint' }, 'keepass-endpoint-file:s' => { name => 'keepass_endpoint_file' }, 'keepass-file:s' => { name => 'keepass_file' }, 'keepass-password:s' => { name => 'keepass_password' }, 'keepass-search-value:s@' => { name => 'keepass_search_value' }, 'keepass-map-option:s@' => { name => 'keepass_map_option' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'KEEPASS OPTIONS'); $self->{output} = $options{output}; $JSON::Path::Safe = 0; return $self; } sub build_api_args { my ($self, %options) = @_; $self->{connection_info} = { file => undef, password => undef }; if (defined($options{option_results}->{keepass_endpoint_file}) && $options{option_results}->{keepass_endpoint_file} ne '') { if (! -f $options{option_results}->{keepass_endpoint_file} or ! -r $options{option_results}->{keepass_endpoint_file}) { $self->{output}->add_option_msg(short_msg => "Cannot read keepass endpoint file: $!"); $self->{output}->option_exit(); } require $options{option_results}->{keepass_endpoint_file}; if (defined($keepass_connections) && defined($options{option_results}->{keepass_endpoint}) && $options{option_results}->{keepass_endpoint} ne '') { if (!defined($keepass_connections->{$options{option_results}->{keepass_endpoint}})) { $self->{output}->add_option_msg(short_msg => "Endpoint $options{option_results}->{keepass_endpoint} doesn't exist in keepass endpoint file"); $self->{output}->option_exit(); } $self->{connection_info} = $keepass_connections->{$options{option_results}->{keepass_endpoint}}; } } foreach (['keepass_file', 'file'], ['keepass_password', 'password']) { if (defined($options{option_results}->{$_->[0]}) && $options{option_results}->{$_->[0]} ne '') { $self->{connection_info}->{$_->[1]} = $options{option_results}->{$_->[0]}; } } if (defined($self->{connection_info}->{file}) && $self->{connection_info}->{file} ne '') { if (!defined($self->{connection_info}->{password}) || $self->{connection_info}->{password} eq '') { $self->{output}->add_option_msg(short_msg => "Please set keepass-password option"); $self->{output}->option_exit(); } } } sub do_lookup { my ($self, %options) = @_; $self->{lookup_values} = {}; return if (!defined($options{option_results}->{keepass_search_value})); foreach (@{$options{option_results}->{keepass_search_value}}) { next if (! /^(.+?)=(.+)$/); my ($map, $lookup) = ($1, $2); # Change %{xxx} options usage while ($lookup =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $options{option_results}->{$1} if (defined($options{option_results}->{$1})); $lookup =~ s/\%\{$1\}/$sub/g } my $jpath = JSON::Path->new($lookup); my $result = $jpath->value($options{json}); $self->{output}->output_add(long_msg => 'lookup = ' . $lookup. ' - response = ' . Data::Dumper::Dumper($result), debug => 1); $self->{lookup_values}->{$map} = $result; } } sub do_map { my ($self, %options) = @_; return if (!defined($options{option_results}->{keepass_map_option})); foreach (@{$options{option_results}->{keepass_map_option}}) { next if (! /^(.+?)=(.+)$/); my ($option, $map) = ($1, $2); # Change %{xxx} options usage while ($map =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $self->{lookup_values}->{$1} if (defined($self->{lookup_values}->{$1})); $map =~ s/\%\{$1\}/$sub/g } $option =~ s/-/_/g; $options{option_results}->{$option} = $map; } } sub manage_options { my ($self, %options) = @_; $self->build_api_args(%options); return if (!defined($self->{connection_info}->{file})); my $keepass = KeePass::Reader->new(); my $content = $keepass->load_db(file => $self->{connection_info}->{file}, password => $self->{connection_info}->{password}); if (!defined($content)) { $self->{output}->add_option_msg(short_msg => "Cannot read keepass file: " . $keepass->error()); $self->{output}->option_exit(); } $self->{output}->output_add(long_msg => Data::Dumper::Dumper($content), debug => 1) if ($self->{output}->is_debug()); $self->do_lookup(%options, json => $content); $self->do_map(%options); } 1; =head1 NAME Keepass global =head1 SYNOPSIS keepass class =head1 KEEPASS OPTIONS =over 8 =item B<--keepass-endpoint> Connection information to be used in keepass file. =item B<--keepass-endpoint-file> File with keepass connection informations. =item B<--keepass-file> Keepass file. =item B<--keepass-password> Keepass master password. =item B<--keepass-search-value> Looking for a value in the JSON keepass. Can use JSON Path and other option values. Example: --keepass-search-value='password=$..entries.[?($_->{title} =~ /serveurx/i)].password' --keepass-search-value='username=$..entries.[?($_->{title} =~ /serveurx/i)].username' --keepass-search-value='password=$..entries.[?($_->{title} =~ /%{hostname}/i)].password' =item B<--keepass-map-option> Overload plugin option. Example: --keepass-map-option="password=%{password}" --keepass-map-option="username=%{username}" =back =head1 DESCRIPTION B<keepass>. =cut CENTREON_PLUGINS_PASSWORDMGR_KEEPASS $fatpacked{"centreon/plugins/passwordmgr/teampass.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PASSWORDMGR_TEAMPASS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::passwordmgr::teampass; use strict; use warnings; use JSON::Path; use JSON::XS; use Data::Dumper; use centreon::plugins::http; use vars qw($teampass_connections); sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class PasswordMgr: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class PasswordMgr: Need to specify 'options' argument."); $options{output}->option_exit(); } $options{options}->add_options(arguments => { "teampass-endpoint:s" => { name => 'teampass_endpoint' }, "teampass-endpoint-file:s" => { name => 'teampass_endpoint_file' }, "teampass-api-key:s" => { name => 'teampass_api_key' }, "teampass-api-address:s" => { name => 'teampass_api_address' }, "teampass-api-request:s" => { name => 'teampass_api_request' }, "teampass-search-value:s@" => { name => 'teampass_search_value' }, "teampass-map-option:s@" => { name => 'teampass_map_option' }, "teampass-timeout:s" => { name => 'teampass_timeout' }, }); $options{options}->add_help(package => __PACKAGE__, sections => 'TEAMPASS OPTIONS'); $self->{output} = $options{output}; $self->{http} = centreon::plugins::http->new(%options, noptions => 1, default_backend => 'curl'); $JSON::Path::Safe = 0; return $self; } sub build_api_args { my ($self, %options) = @_; $self->{connection_info} = { address => undef, key => undef, request => undef }; if (defined($options{option_results}->{teampass_endpoint_file}) && $options{option_results}->{teampass_endpoint_file} ne '') { if (! -f $options{option_results}->{teampass_endpoint_file} or ! -r $options{option_results}->{teampass_endpoint_file}) { $self->{output}->add_option_msg(short_msg => "Cannot read teampass file: $!"); $self->{output}->option_exit(); } require $options{option_results}->{teampass_endpoint_file}; if (defined($teampass_connections) && defined($options{option_results}->{teampass_endpoint}) && $options{option_results}->{teampass_endpoint} ne '') { if (!defined($teampass_connections->{$options{option_results}->{teampass_endpoint}})) { $self->{output}->add_option_msg(short_msg => "Endpoint $options{option_results}->{teampass_endpoint} doesn't exist in teampass file"); $self->{output}->option_exit(); } $self->{connection_info} = $teampass_connections->{$options{option_results}->{teampass_endpoint}}; } } foreach (['teampass_api_address', 'address'], ['teampass_api_key', 'key'], ['teampass_api_request', 'request']) { if (defined($options{option_results}->{$_->[0]}) && $options{option_results}->{$_->[0]} ne '') { $self->{connection_info}->{$_->[1]} = $options{option_results}->{$_->[0]}; } } if (defined($self->{connection_info}->{address}) && $self->{connection_info}->{address} ne '') { foreach ('key', 'request') { if (!defined($self->{connection_info}->{$_}) || $self->{connection_info}->{$_} eq '') { $self->{output}->add_option_msg(short_msg => "Please set teampass-api-$_ option"); $self->{output}->option_exit(); } } } } sub do_lookup { my ($self, %options) = @_; $self->{lookup_values} = {}; return if (!defined($options{option_results}->{teampass_search_value})); foreach (@{$options{option_results}->{teampass_search_value}}) { next if (! /^(.+?)=(.+)$/); my ($map, $lookup) = ($1, $2); # Change %{xxx} options usage while ($lookup =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $options{option_results}->{$1} if (defined($options{option_results}->{$1})); $lookup =~ s/\%\{$1\}/$sub/g } my $jpath = JSON::Path->new($lookup); my $result = $jpath->value($options{json}); $self->{output}->output_add(long_msg => 'lookup = ' . $lookup. ' - response = ' . Data::Dumper::Dumper($result), debug => 1); $self->{lookup_values}->{$map} = $result; } } sub do_map { my ($self, %options) = @_; return if (!defined($options{option_results}->{teampass_map_option})); foreach (@{$options{option_results}->{teampass_map_option}}) { next if (! /^(.+?)=(.+)$/); my ($option, $map) = ($1, $2); # Change %{xxx} options usage while ($map =~ /\%\{(.*?)\}/g) { my $sub = ''; $sub = $self->{lookup_values}->{$1} if (defined($self->{lookup_values}->{$1})); $map =~ s/\%\{$1\}/$sub/g } $option =~ s/-/_/g; $options{option_results}->{$option} = $map; } } sub manage_options { my ($self, %options) = @_; $self->build_api_args(%options); return if (!defined($self->{connection_info}->{address})); $self->{http}->set_options( timeout => $options{option_results}->{teampass_timeout}, unknown_status => '%{http_code} < 200 or %{http_code} >= 300', ); my $response = $self->{http}->request(method => 'GET', full_url => $self->{connection_info}->{address} . $self->{connection_info}->{request}, hostname => '', get_param => ['apikey=' . $self->{connection_info}->{key}], ); $self->{output}->output_add(long_msg => $response, debug => 1); my $json; eval { $json = JSON::XS->new->utf8->decode($response); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode teampass json response: $@"); $self->{output}->option_exit(); } $self->do_lookup(%options, json => $json); $self->do_map(%options); } 1; =head1 NAME Teampass global =head1 SYNOPSIS teampass class =head1 TEAMPASS OPTIONS =over 8 =item B<--teampass-endpoint> Connection information to be used in teampass file. =item B<--teampass-endpoint-file> File with teampass connection informations. =item B<--teampass-timeout> Set HTTP Rest API timeout (default: 5). =item B<--teampass-api-key> Teampass API Key. =item B<--teampass-api-address> Teampass URL (example: http://10.0.0.1/teampass). =item B<--teampass-api-request> Teampass request (example: /api/index.php/folder/3). =item B<--teampass-search-value> Looking for a value in the JSON teampass response. Can use JSON Path and other option values. Example: --teampass-search-value='password=$.[?($_->{label} =~ /serveur1/i)].pw' --teampass-search-value='login=$.[?($_->{label} =~ /serveur1/i)].login' --teampass-search-value='password=$.[?($_->{label} =~ /%{hostname}/i)].pw' =item B<--teampass-map-option> Overload plugin option. Example: --teampass-map-option="password=%{password}" --teampass-map-option="username=%{login}" =back =head1 DESCRIPTION B<teampass>. =cut CENTREON_PLUGINS_PASSWORDMGR_TEAMPASS $fatpacked{"centreon/plugins/perfdata.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_PERFDATA'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::perfdata; use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; $self->{output} = $options{output}; # Typical Nagios Perfdata 'with ~ @ ..' $self->{threshold_label} = {}; $self->{float_precision} = defined($self->{output}->{option_results}->{float_precision}) && $self->{output}->{option_results}->{float_precision} =~ /\d+/ ? int($self->{output}->{option_results}->{float_precision}) : 8; return $self; } sub get_perfdata_for_output { my ($self, %options) = @_; # $options{label} : threshold label # $options{total} : percent threshold to transform in global # $options{cast_int} : cast absolute to int # $options{op} : operator to apply to start/end value (uses with 'value'}) # $options{value} : value to apply with 'op' option if (!defined($self->{threshold_label}->{$options{label}}->{value}) || $self->{threshold_label}->{$options{label}}->{value} eq '') { return ''; } my %perf_value = %{$self->{threshold_label}->{$options{label}}}; if (defined($options{op}) && defined($options{value})) { eval "\$perf_value{start} = \$perf_value{start} $options{op} \$options{value}" if ($perf_value{infinite_neg} == 0); eval "\$perf_value{end} = \$perf_value{end} $options{op} \$options{value}" if ($perf_value{infinite_pos} == 0); } if (defined($options{total})) { $perf_value{start} = $perf_value{start} * $options{total} / 100 if ($perf_value{infinite_neg} == 0); $perf_value{end} = $perf_value{end} * $options{total} / 100 if ($perf_value{infinite_pos} == 0); $perf_value{start} = sprintf("%.2f", $perf_value{start}) if ($perf_value{infinite_neg} == 0 && (!defined($options{cast_int}) || $options{cast_int} != 1)); $perf_value{end} = sprintf("%.2f", $perf_value{end}) if ($perf_value{infinite_pos} == 0 && (!defined($options{cast_int}) || $options{cast_int} != 1)); } $perf_value{start} = int($perf_value{start}) if ($perf_value{infinite_neg} == 0 && defined($options{cast_int}) && $options{cast_int} == 1); $perf_value{end} = int($perf_value{end}) if ($perf_value{infinite_pos} == 0 && defined($options{cast_int}) && $options{cast_int} == 1); my $perf_output = ($perf_value{arobase} == 1 ? '@' : '') . (($perf_value{infinite_neg} == 0) ? $perf_value{start} : '~') . ':' . (($perf_value{infinite_pos} == 0) ? $perf_value{end} : ''); return $perf_output; } sub threshold_validate { my ($self, %options) = @_; # $options{label} : threshold label # $options{value} : threshold value my $status = 1; $self->{threshold_label}->{$options{label}} = { value => $options{value}, start => undef, end => undef, arobase => undef, infinite_neg => undef, infinite_pos => undef }; if (!defined($options{value}) || $options{value} eq '') { return $status; } ($status, my $result_perf) = centreon::plugins::misc::parse_threshold(threshold => $options{value}); $self->{threshold_label}->{$options{label}} = { %{$self->{threshold_label}->{$options{label}}}, %$result_perf }; $self->{threshold_label}->{$options{label}}->{start_precision} = $self->{threshold_label}->{$options{label}}->{start}; if ($self->{threshold_label}->{$options{label}}->{start} =~ /[.,]/) { $self->{threshold_label}->{$options{label}}->{start_precision} = sprintf("%.$self->{output}->{option_results}->{float_precision}f", $self->{threshold_label}->{$options{label}}->{start}); } $self->{threshold_label}->{$options{label}}->{end_precision} = $self->{threshold_label}->{$options{label}}->{end}; if ($self->{threshold_label}->{$options{label}}->{end} =~ /[.,]/) { $self->{threshold_label}->{$options{label}}->{end_precision} = sprintf("%.$self->{output}->{option_results}->{float_precision}f", $self->{threshold_label}->{$options{label}}->{end}); } return $status; } sub threshold_check { my ($self, %options) = @_; # Can check multiple threshold. First match: out. Order is important # options{value}: value to compare # options{threshold}: ref to an array (example: [ {label => 'warning', exit_litteral => 'warning' }, {label => 'critical', exit_litteral => 'critical'} ] if ($options{value} =~ /[.,]/) { $options{value} = sprintf("%.$self->{output}->{option_results}->{float_precision}f", $options{value}); } foreach (@{$options{threshold}}) { next if (!defined($self->{threshold_label}->{$_->{label}})); next if (!defined($self->{threshold_label}->{$_->{label}}->{value}) || $self->{threshold_label}->{$_->{label}}->{value} eq ''); if ($self->{threshold_label}->{$_->{label}}->{arobase} == 0 && ($options{value} < $self->{threshold_label}->{$_->{label}}->{start_precision} || $options{value} > $self->{threshold_label}->{$_->{label}}->{end_precision})) { return $_->{exit_litteral}; } elsif ($self->{threshold_label}->{$_->{label}}->{arobase} == 1 && ($options{value} >= $self->{threshold_label}->{$_->{label}}->{start_precision} && $options{value} <= $self->{threshold_label}->{$_->{label}}->{end_precision})) { return $_->{exit_litteral}; } } return 'ok'; } sub trim { my ($self, $value) = @_; $value =~ s/^[ \t]+//; $value =~ s/[ \t]+$//; return $value; } sub change_bytes { my ($self, %options) = @_; my $value = $options{value}; my $divide = defined($options{network}) ? 1000 : 1024; my @units = ('K', 'M', 'G', 'T'); my $unit = ''; my $sign = ''; $sign = '-' if ($value != abs($value)); $value = abs($value); for (my $i = 0; $i < scalar(@units); $i++) { last if (($value / $divide) < 1); $unit = $units[$i]; $value = $value / $divide; } return (sprintf('%.2f', $sign . $value), $unit . (defined($options{network}) ? 'b' : 'B')); } 1; =head1 NAME Perfdata class =head1 SYNOPSIS - =head1 DESCRIPTION B<perfdata>. =cut CENTREON_PLUGINS_PERFDATA $fatpacked{"centreon/plugins/script.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_SCRIPT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::script; use strict; use warnings; use centreon::plugins::output; use centreon::plugins::misc; use Pod::Usage; my %handlers = (DIE => {}, ALRM => {}); my $global_version = '20240909 (9496729)'; my $alternative_fatpacker = 1; sub new { my ($class) = @_; my $self = {}; bless $self, $class; $self->{options} = undef; $self->{plugin} = undef; $self->{help} = undef; # Avoid to destroy because it keeps a ref on the object. # A problem if we execute it multiple times in the same perl execution # Use prepare_destroy $self->set_signal_handlers(); return $self; } sub prepare_destroy { my ($self) = @_; %handlers = (); } sub set_signal_handlers { my ($self) = @_; $SIG{__DIE__} = \&class_handle_DIE; $handlers{DIE}->{$self} = sub { $self->handle_DIE($_[0]) }; } sub class_handle_DIE { my ($msg) = @_; foreach (keys %{$handlers{DIE}}) { &{$handlers{DIE}->{$_}}($msg); } } sub class_handle_ALRM { foreach (keys %{$handlers{ALRM}}) { &{$handlers{ALRM}->{$_}}(); } } sub handle_DIE { my ($self, $msg) = @_; return unless defined $^S and $^S == 0; # Ignore errors in eval $self->{output}->add_option_msg(short_msg => $msg); $self->{output}->die_exit(); } sub handle_ALRM { my ($self) = @_; $self->{output}->add_option_msg(short_msg => 'script global timeout'); $self->{output}->option_exit(); } sub get_global_version { return $global_version; } sub get_plugin { my ($self) = @_; # Need to load global 'Output' and 'Options' if ($alternative_fatpacker == 0) { require centreon::plugins::options; $self->{options} = centreon::plugins::options->new(); } else { require centreon::plugins::alternative::FatPackerOptions; $self->{options} = centreon::plugins::alternative::FatPackerOptions->new(); } $self->{output} = centreon::plugins::output->new(options => $self->{options}); $self->{options}->set_output(output => $self->{output}); $self->{options}->add_options(arguments => { 'plugin:s' => { name => 'plugin' }, 'list-plugin' => { name => 'list_plugin' }, 'help' => { name => 'help' }, 'ignore-warn-msg' => { name => 'ignore_warn_msg' }, 'version' => { name => 'version' }, 'runas:s' => { name => 'runas' }, 'global-timeout:s' => { name => 'global_timeout' }, 'environment:s%' => { name => 'environment' }, 'convert-args:s' => { name => 'convert_args' }, }); $self->{options}->parse_options(); $self->{plugin} = $self->{options}->get_option(argument => 'plugin'); $self->{list_plugin} = $self->{options}->get_option(argument => 'list_plugin'); $self->{help} = $self->{options}->get_option(argument => 'help'); $self->{version} = $self->{options}->get_option(argument => 'version'); $self->{runas} = $self->{options}->get_option(argument => 'runas'); $self->{environment} = $self->{options}->get_option(argument => 'environment'); $self->{ignore_warn_msg} = $self->{options}->get_option(argument => 'ignore_warn_msg'); $self->{convert_args} = $self->{options}->get_option(argument => 'convert_args'); my $global_timeout = $self->{options}->get_option(argument => 'global_timeout'); if (defined($global_timeout) && $global_timeout =~ /(\d+)/) { $SIG{ALRM} = \&class_handle_ALRM; $handlers{ALRM}->{$self} = sub { $self->handle_ALRM() }; alarm($1); } $self->{output}->plugin(name => $self->{plugin}); $self->{output}->check_options(option_results => $self->{options}->get_options()); $self->{options}->clean(); } sub convert_args { my ($self) = @_; if ($self->{convert_args} =~ /^(.+?),(.*)/) { my ($search, $replace) = ($1, $2); for (my $i = 0; $i <= $#ARGV; $i++) { eval "\$ARGV[\$i] =~ s/$search/$replace/g"; } } } sub display_local_help { my ($self) = @_; my $stdout; if ($self->{help}) { local *STDOUT; open STDOUT, '>', \$stdout; if ($alternative_fatpacker == 0) { pod2usage(-exitval => 'NOEXIT', -input => $self->{options}->pod_where(package => __PACKAGE__)); } else { my $pp = __PACKAGE__ . '.pm'; $pp =~ s{::}{/}g; my $content_class = $INC{$pp}->{$pp}; open my $str_fh, '<', \$content_class; pod2usage(-exitval => 'NOEXIT', -input => $str_fh); close $str_fh; } } $self->{output}->add_option_msg(long_msg => $stdout) if (defined($stdout)); } sub check_directory { my ($self, $directory) = @_; opendir(my $dh, $directory) || return ; while (my $filename = readdir $dh) { $self->check_directory($directory . '/' . $filename) if ($filename !~ /^\./ && -d $directory . '/' . $filename); if ($filename eq 'plugin.pm') { my $stdout = ''; { local *STDOUT; open STDOUT, '>', \$stdout; pod2usage( -exitval => 'NOEXIT', -input => $directory . "/" . $filename, -verbose => 99, -sections => 'PLUGIN DESCRIPTION' ); } $self->{plugins_result}->{$directory . '/' . $filename} = $stdout; } } closedir $dh; } sub fatpacker_find_plugin { my ($self) = @_; my $plugins = []; foreach (@INC) { next if (ref($_) !~ /FatPacked/); foreach my $name (keys %$_) { if ($name =~ /plugin.pm$/) { push @$plugins, $name; } } } return $plugins; } sub check_plugin_option { my ($self) = @_; if (defined($self->{version})) { $self->{output}->add_option_msg(short_msg => 'Global Version: ' . $global_version); $self->{output}->option_exit(nolabel => 1); } my $no_plugin = 1; if ($alternative_fatpacker == 1) { my $integrated_plugins = $self->fatpacker_find_plugin(); if (scalar(@$integrated_plugins) == 1) { $self->{plugin} = $integrated_plugins->[0]; $no_plugin = 0; } } if ($no_plugin == 1) { $self->{output}->add_option_msg(short_msg => "Need to specify '--plugin' option."); $self->{output}->option_exit(); } } sub display_list_plugin { my ($self) = @_; $self->{plugins_result} = {}; if ($alternative_fatpacker == 1) { my $integrated_plugins = $self->fatpacker_find_plugin(); foreach my $key (sort @$integrated_plugins) { # Need to load it to get the description centreon::plugins::misc::mymodule_load( output => $self->{output}, module => $key, error_msg => 'Cannot load module --plugin.' ); my $name = $key; $name =~ s/\.pm//g; $name =~ s/\//::/g; $self->{output}->add_option_msg(long_msg => '-----------------'); $self->{output}->add_option_msg(long_msg => 'PLUGIN: ' . $name); { my $stdout = ''; local *STDOUT; open STDOUT, '>', \$stdout; my $content_class = $INC{$key}->{$key}; open my $str_fh, '<', \$content_class; pod2usage(-exitval => 'NOEXIT', -input => $str_fh, -verbose => 99, -sections => 'PLUGIN DESCRIPTION'); close $str_fh; $self->{output}->add_option_msg(long_msg => $stdout); } } return ; } centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'FindBin', error_msg => "Cannot load module 'FindBin'." ); my $directory = $FindBin::Bin; if (defined($ENV{PAR_TEMP})) { $directory = $ENV{PAR_TEMP} . '/inc/lib'; } # Search file 'plugin.pm' $self->check_directory($directory); foreach my $key (sort keys %{$self->{plugins_result}}) { my $name = $key; $name =~ s/^\Q$directory\E\/(.*)\.pm/$1/; $name =~ s/\//::/g; $self->{plugins_result}->{$key} =~ s/^Plugin Description/DESCRIPTION/i; $self->{output}->add_option_msg(long_msg => '-----------------'); $self->{output}->add_option_msg(long_msg => 'PLUGIN: ' . $name); $self->{output}->add_option_msg(long_msg => $self->{plugins_result}->{$key}); } } sub check_relaunch_get_args { my ($self) = @_; my $args = ['--plugin=' . $self->{plugin}, @ARGV]; push @$args, '--ignore-warn-msg' if (defined($self->{ignore_warn_msg})); push @$args, '--help' if (defined($self->{help})); push @$args, '--global-timeout', $self->{global_timeout} if (defined($self->{global_timeout})); foreach (( ['output_xml', 0], ['output_json', 0], ['output_openmetrics', 0], ['disco_format', 0], ['disco_show', 0], ['use_new_perfdata', 0], ['debug', 0], ['verbose', 0], ['range_perfdata', 1], ['filter_uom', 1], ['opt_exit', 1], ['filter_perfdata', 1], ['output_file', 1], ['float_precision', 1] )) { my $option = $self->{output}->get_option(option => $_->[0]); if (defined($option)) { my $option_label = $_->[0]; $option_label =~ s/_/-/g; push @$args, "--$option_label" if ($_->[1] == 0); push @$args, "--$option_label", $option if ($_->[1] == 1); } } return $args; } sub check_relaunch { my $self = shift; centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'FindBin', error_msg => "Cannot load module 'FindBin'." ); my $need_restart = 0; my $cmd = $FindBin::Bin . '/' . $FindBin::Script; my $args = []; if (defined($self->{environment})) { foreach (keys %{$self->{environment}}) { if ($_ ne '' && (!defined($ENV{$_}) || $ENV{$_} ne $self->{environment}->{$_})) { $ENV{$_} = $self->{environment}->{$_}; $need_restart = 1; } } } my $rebuild_args = $self->check_relaunch_get_args(); if (defined($self->{runas}) && $self->{runas} ne '') { # Check if it's already me and user exist ;) my ($name, $passwd, $uid) = getpwnam($self->{runas}); if (!defined($uid)) { $self->{output}->add_option_msg(short_msg => "Runas user '" . $self->{runas} . "' not exist."); $self->{output}->option_exit(); } if ($uid != $>) { if ($> == 0) { unshift @$args, '-s', '/bin/bash', '-l', $self->{runas}, '-c', join(' ', $cmd, $rebuild_args); $cmd = 'su'; } else { unshift @$args, '-S', '-u', $self->{runas}, $cmd, @$rebuild_args; $cmd = 'sudo'; } $need_restart = 1; } } if ($need_restart == 1) { if (scalar(@$args) <= 0) { unshift @$args, @$rebuild_args; } my ($lerror, $stdout, $exit_code) = centreon::plugins::misc::backtick( command => $cmd, arguments => $args, timeout => 30, wait_exit => 1 ); if ($exit_code <= -1000) { if ($exit_code == -1000) { $self->{output}->output_add( severity => 'UNKNOWN', short_msg => $stdout ); } $self->{output}->display(); $self->{output}->exit(); } chomp $stdout; print $stdout . "\n"; # We put unknown if (!($exit_code >= 0 && $exit_code <= 4)) { exit 3; } exit $exit_code; } } sub run { my ($self) = @_; $self->get_plugin(); if (defined($self->{help}) && !defined($self->{plugin})) { $self->display_local_help(); $self->{output}->option_exit(); } if (defined($self->{list_plugin})) { $self->display_list_plugin(); $self->{output}->option_exit(); } $self->check_plugin_option() if (!defined($self->{plugin}) || $self->{plugin} eq ''); if (defined($self->{ignore_warn_msg})) { $SIG{__WARN__} = sub {}; } $self->convert_args() if (defined($self->{convert_args})); $self->check_relaunch(); (undef, $self->{plugin}) = centreon::plugins::misc::mymodule_load( output => $self->{output}, module => $self->{plugin}, error_msg => 'Cannot load module --plugin.' ); my $plugin = $self->{plugin}->new(options => $self->{options}, output => $self->{output}); $plugin->init( help => $self->{help}, version => $self->{version} ); $plugin->run(); } 1; =head1 NAME centreon_plugins.pl - main program to call Centreon plugins. =head1 SYNOPSIS centreon_plugins.pl [options] =head1 OPTIONS =over 8 =item B<--plugin> Specify the path to the plugin. =item B<--list-plugin> List all available plugins. =item B<--version> Return the version of the plugin. =item B<--help> Return the help message for the plugin and exit. =item B<--ignore-warn-msg> Ignore Perl warning messages (they will not be displayed). =item B<--runas> Run the script as a different user. =item B<--global-timeout> Define the script's timeout. =item B<--environment> Define environment variables for the script (set them in the execution environment before running it for better performance). =item B<--convert-args> Replace a pattern in the provided arguments. Useful to bypass forbidden characters. E.g.: when a password transmitted via the NRPE protocol contains '!' (which is interpreted as a separator), you can send '##' instead of the '!' and the plugin will replace '##' with '!', using the --convert-args='##,\x21' option. =back =head1 DESCRIPTION B<centreon_plugins.pl> . =cut CENTREON_PLUGINS_SCRIPT $fatpacked{"centreon/plugins/script_custom.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_SCRIPT_CUSTOM'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::script_custom; use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; $self->{options} = $options{options}; $self->{output} = $options{output}; $self->{options}->add_options( arguments => { 'mode:s' => { name => 'mode_name' }, 'dyn-mode:s' => { name => 'dynmode_name' }, 'list-mode' => { name => 'list_mode' }, 'custommode:s' => { name => 'custommode_name' }, 'list-custommode' => { name => 'list_custommode' }, 'multiple' => { name => 'multiple' }, 'no-sanity-options' => { name => 'no_sanity_options' }, 'pass-manager:s' => { name => 'pass_manager' } } ); $self->{version} = '1.0'; $self->{modes} = {}; $self->{custom_modes} = {}; $self->{default} = undef; $self->{customdefault} = {}; $self->{custommode_current} = undef; $self->{custommode_stored} = []; $self->{options}->parse_options(); $self->{option_results} = $self->{options}->get_options(); foreach (keys %{$self->{option_results}}) { $self->{$_} = $self->{option_results}->{$_}; } $self->{options}->clean(); $self->{options}->add_help(package => $options{package}, sections => 'PLUGIN DESCRIPTION'); $self->{options}->add_help(package => __PACKAGE__, sections => 'GLOBAL OPTIONS'); $self->{output}->mode(name => $self->{mode_name}); return $self; } sub load_custom_mode { my ($self, %options) = @_; $self->is_custommode(custommode => $self->{custommode_name}); centreon::plugins::misc::mymodule_load( output => $self->{output}, module => $self->{custom_modes}->{$self->{custommode_name}}, error_msg => 'Cannot load module --custommode.' ); $self->{custommode_current} = $self->{custom_modes}->{$self->{custommode_name}}->new( options => $self->{options}, output => $self->{output}, custommode_name => $self->{custommode_name}, mode_name => $self->{mode_name} ); } sub init { my ($self, %options) = @_; # add meta mode $self->{modes}->{multi} = 'centreon::plugins::multi'; if (defined($options{help}) && !defined($self->{mode_name}) && !defined($self->{dynmode_name})) { $self->{options}->display_help(); $self->{output}->option_exit(); } if (defined($options{version}) && !defined($self->{mode_name})&& !defined($self->{dynmode_name})) { $self->version(); } if (defined($self->{list_mode})) { $self->list_mode(); } if (defined($self->{list_custommode})) { $self->list_custommode(); } $self->{options}->set_sanity() if (!defined($self->{no_sanity_options})); # Output HELP $self->{options}->add_help(package => 'centreon::plugins::output', sections => 'OUTPUT OPTIONS'); $self->load_password_mgr(); if (defined($self->{custommode_name}) && $self->{custommode_name} ne '') { $self->load_custom_mode(); } elsif (scalar(keys %{$self->{custom_modes}}) == 1) { $self->{custommode_name} = (keys(%{$self->{custom_modes}}))[0]; $self->load_custom_mode(); } else { $self->{output}->add_option_msg(short_msg => "Need to specify '--custommode'."); $self->{output}->option_exit(); } # Load mode if (defined($self->{mode_name}) && $self->{mode_name} ne '') { $self->is_mode(mode => $self->{mode_name}); centreon::plugins::misc::mymodule_load(output => $self->{output}, module => $self->{modes}{$self->{mode_name}}, error_msg => "Cannot load module --mode."); $self->{mode} = $self->{modes}{$self->{mode_name}}->new(options => $self->{options}, output => $self->{output}, mode => $self->{mode_name}); } elsif (defined($self->{dynmode_name}) && $self->{dynmode_name} ne '') { (undef, $self->{dynmode_name}) = centreon::plugins::misc::mymodule_load(output => $self->{output}, module => $self->{dynmode_name}, error_msg => "Cannot load module --dyn-mode."); $self->{mode} = $self->{dynmode_name}->new(options => $self->{options}, output => $self->{output}, mode => $self->{dynmode_name}); } else { $self->{output}->add_option_msg(short_msg => "Need to specify '--mode' or '--dyn-mode' option."); $self->{output}->option_exit(); } if (defined($options{help})) { if (defined($self->{mode_name}) && $self->{mode_name} ne '') { $self->{options}->add_help(package => $self->{modes}{$self->{mode_name}}, sections => 'MODE'); } else { $self->{options}->add_help(package => $self->{dynmode_name}, sections => 'MODE'); } $self->{options}->display_help(); $self->{output}->option_exit(); } if (defined($options{version})) { $self->{mode}->version(); $self->{output}->option_exit(nolabel => 1); } $self->{options}->parse_options(); $self->{option_results} = $self->{options}->get_options(); $self->{pass_mgr}->manage_options(option_results => $self->{option_results}) if (defined($self->{pass_mgr})); push @{$self->{custommode_stored}}, $self->{custommode_current}; $self->{custommode_current}->set_options(option_results => $self->{option_results}); $self->{custommode_current}->set_defaults(default => $self->{customdefault}); while ($self->{custommode_current}->check_options()) { $self->{custommode_current} = $self->{custom_modes}->{$self->{custommode_name}}->new(noptions => 1, options => $self->{options}, output => $self->{output}, mode => $self->{custommode_name}); $self->{custommode_current}->set_options(option_results => $self->{option_results}); push @{$self->{custommode_stored}}, $self->{custommode_current}; } $self->{mode}->check_options( option_results => $self->{option_results}, default => $self->{default}, modes => $self->{modes} # for meta mode multi ); } sub load_password_mgr { my ($self, %options) = @_; return if (!defined($self->{option_results}->{pass_manager}) || $self->{option_results}->{pass_manager} eq ''); (undef, my $pass_mgr_name) = centreon::plugins::misc::mymodule_load( output => $self->{output}, module => "centreon::plugins::passwordmgr::" . $self->{option_results}->{pass_manager}, error_msg => "Cannot load module 'centreon::plugins::passwordmgr::" . $self->{option_results}->{pass_manager} . "'" ); $self->{pass_mgr} = $pass_mgr_name->new(options => $self->{options}, output => $self->{output}); } sub run { my $self = shift; if ($self->{output}->is_disco_format()) { $self->{mode}->disco_format(); $self->{output}->display_disco_format(); $self->{output}->exit(exit_litteral => 'ok'); } if ($self->{output}->is_disco_show()) { if (defined($self->{multiple})) { $self->{mode}->disco_show(custom => $self->{custommode}); } else { $self->{mode}->disco_show(custom => $self->{custommode_stored}[0]); } $self->{output}->display_disco_show(); $self->{output}->exit(exit_litteral => 'ok'); } else { if (defined($self->{multiple})) { $self->{mode}->run(custom => $self->{custommode_stored}); } else { $self->{mode}->run(custom => $self->{custommode_stored}[0]); } } } sub is_mode { my ($self, %options) = @_; if (!defined($self->{modes}{$options{mode}})) { $self->{output}->add_option_msg(short_msg => "mode '" . $options{mode} . "' doesn't exist (use --list-mode option to show available modes)."); $self->{output}->option_exit(); } } sub is_custommode { my ($self, %options) = @_; if (!defined($self->{custom_modes}->{$options{custommode}})) { $self->{output}->add_option_msg(short_msg => "mode '" . $options{custommode} . "' doesn't exist (use --list-custommode option to show available modes)."); $self->{output}->option_exit(); } } sub version { my $self = shift; $self->{output}->add_option_msg(short_msg => "Plugin Version: " . $self->{version}); $self->{output}->option_exit(nolabel => 1); } sub list_mode { my $self = shift; $self->{options}->display_help(); $self->{output}->add_option_msg(long_msg => 'Modes Meta:'); $self->{output}->add_option_msg(long_msg => ' multi'); $self->{output}->add_option_msg(long_msg => ''); $self->{output}->add_option_msg(long_msg => 'Modes Available:'); foreach (sort keys %{$self->{modes}}) { next if ($_ eq 'multi'); $self->{output}->add_option_msg(long_msg => ' ' . $_); } $self->{output}->option_exit(nolabel => 1); } sub list_custommode { my $self = shift; $self->{options}->display_help(); $self->{output}->add_option_msg(long_msg => "Custom Modes Available:"); foreach (keys %{$self->{custom_modes}}) { $self->{output}->add_option_msg(long_msg => " " . $_); } $self->{output}->option_exit(nolabel => 1); } 1; =head1 NAME - =head1 SYNOPSIS - =head1 GLOBAL OPTIONS =over 8 =item B<--mode> Define the mode in which you want the plugin to be executed (see --list-mode). =item B<--dyn-mode> Specify a mode with the module's path (advanced). =item B<--list-mode> List all available modes. =item B<--mode-version> Check minimal version of mode. If not, unknown error. =item B<--version> Return the version of the plugin. =item B<--custommode> When a plugin offers several ways (CLI, library, etc.) to get information the desired one must be defined with this option. =item B<--list-custommode> List all available custom modes. =item B<--multiple> Multiple custom mode objects. This may be required by some specific modes (advanced). =item B<--pass-manager> Define the password manager you want to use. Supported managers are: environment, file, keepass, hashicorpvault and teampass. =back =head1 DESCRIPTION B<>. =cut CENTREON_PLUGINS_SCRIPT_CUSTOM $fatpacked{"centreon/plugins/snmp.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_SNMP'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::snmp; use strict; use warnings; use centreon::plugins::misc; use SNMP; use Socket; use POSIX; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; # $options{options} = options object # $options{output} = output object # $options{exit_value} = integer if (!defined($options{output})) { print "Class SNMP: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class SNMP: Need to specify 'options' argument."); $options{output}->option_exit(); } if (!defined($options{noptions})) { $options{options}->add_options(arguments => { 'hostname|host:s' => { name => 'host' }, 'snmp-community:s' => { name => 'snmp_community', default => 'public' }, 'snmp-version:s' => { name => 'snmp_version', default => 1 }, 'snmp-port:s' => { name => 'snmp_port', default => 161 }, 'snmp-timeout:s' => { name => 'snmp_timeout', default => 1 }, 'snmp-retries:s' => { name => 'snmp_retries', default => 5 }, 'maxrepetitions:s' => { name => 'maxrepetitions', default => 50 }, 'subsetleef:s' => { name => 'subsetleef', default => 50 }, 'subsettable:s' => { name => 'subsettable', default => 100 }, 'snmp-cache-file:s' => { name => 'snmp_cache_file' }, 'snmp-autoreduce:s' => { name => 'snmp_autoreduce' }, 'snmp-force-getnext' => { name => 'snmp_force_getnext' }, 'snmp-username:s' => { name => 'snmp_security_name' }, 'authpassphrase:s' => { name => 'snmp_auth_passphrase' }, 'authprotocol:s' => { name => 'snmp_auth_protocol' }, 'privpassphrase:s' => { name => 'snmp_priv_passphrase' }, 'privprotocol:s' => { name => 'snmp_priv_protocol' }, 'contextname:s' => { name => 'snmp_context_name' }, 'contextengineid:s' => { name => 'snmp_context_engine_id' }, 'securityengineid:s' => { name => 'snmp_security_engine_id' }, 'snmp-tls-transport:s' => { name => 'snmp_tls_transport' }, 'snmp-tls-our-identity:s' => { name => 'snmp_tls_our_identity' }, 'snmp-tls-their-identity:s' => { name => 'snmp_tls_their_identity' }, 'snmp-tls-their-hostname:s' => { name => 'snmp_tls_their_hostname' }, 'snmp-tls-trust-cert:s ' => { name => 'snmp_tls_trust_cert' }, 'snmp-errors-exit:s' => { name => 'snmp_errors_exit', default => 'unknown' }, }); $options{options}->add_help(package => __PACKAGE__, sections => 'SNMP OPTIONS'); } ##### $self->{session} = undef; $self->{output} = $options{output}; $self->{snmp_params} = {}; $self->{use_snmp_cache} = 0; # Dont load MIB $SNMP::auto_init_mib = 0; $ENV{MIBS} = ''; # For snmpv v1 - get request retries when you have "NoSuchName" $self->{RetryNoSuch} = 1; # Dont try to translate OID (we keep value) $self->{UseNumeric} = 1; $self->{error_msg} = undef; $self->{error_status} = 0; return $self; } sub connect { my ($self, %options) = @_; $self->{snmp_params}->{RetryNoSuch} = $self->{RetryNoSuch}; $self->{snmp_params}->{UseNumeric} = $self->{UseNumeric}; if (!$self->{output}->is_litteral_status(status => $self->{snmp_errors_exit})) { $self->{output}->add_option_msg(short_msg => "Unknown value '" . $self->{snmp_errors_exit} . "' for --snmp-errors-exit."); $self->{output}->option_exit(exit_litteral => 'unknown'); } $self->{session} = new SNMP::Session(%{$self->{snmp_params}}); if (!defined($self->{session})) { if (defined($options{dont_quit}) && $options{dont_quit} == 1) { $self->set_error(error_status => -1, error_msg => 'SNMP Session: unable to create'); return 1; } $self->{output}->add_option_msg(short_msg => 'SNMP Session: unable to create'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } if ($self->{session}->{ErrorNum}) { if (defined($options{dont_quit}) && $options{dont_quit} == 1) { $self->set_error(error_status => -1, error_msg => 'SNMP Session: ' . $self->{session}->{ErrorStr}); return 1; } $self->{output}->add_option_msg(short_msg => 'SNMP Session: ' . $self->{session}->{ErrorStr}); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } return 0; } sub load { my ($self, %options) = @_; # $options{oids} = ref to array of oids (example: ['.1.2', '.1.2']) # $options{instances} = ref to array of oids instances # $options{begin}, $args->{end} = integer instance end # $options{instance_regexp} = str # 3 way to use: with instances, with end, none if (defined($options{end})) { for (my $i = $options{begin}; $i <= $options{end}; $i++) { foreach (@{$options{oids}}) { push @{$self->{oids_loaded}}, $_ . "." . $i; } } return ; } if (defined($options{instances})) { $options{instance_regexp} = defined($options{instance_regexp}) ? $options{instance_regexp} : '(\d+)$'; foreach my $instance (@{$options{instances}}) { $instance =~ /$options{instance_regexp}/; foreach (@{$options{oids}}) { push @{$self->{oids_loaded}}, $_ . "." . $1; } } return ; } push @{$self->{oids_loaded}}, @{$options{oids}}; } sub autoreduce_table { my ($self, %options) = @_; return 1 if (defined($self->{snmp_force_getnext}) || $self->is_snmpv1()); if ($self->{snmp_params}->{Retries} > 1) { $self->{snmp_params}->{Retries} = 1; $self->connect(); } return 1 if (${$options{repeat_count}} == 1); ${$options{repeat_count}} = int(${$options{repeat_count}} / $self->{snmp_autoreduce_divisor}); ${$options{repeat_count}} = 1 if (${$options{repeat_count}} < 1); return 0; } sub autoreduce_multiple_table { my ($self, %options) = @_; if ($self->{snmp_params}->{Retries} > 1) { $self->{snmp_params}->{Retries} = 1; $self->connect(); } return 1 if (${$options{repeat_count}} == 1); ${$options{repeat_count}} = int(${$options{repeat_count}} / $self->{snmp_autoreduce_divisor}); $self->{subsettable} = int($self->{subsettable} / $self->{snmp_autoreduce_divisor}); ${$options{repeat_count}} = 1 if (${$options{repeat_count}} < 1); return 0; } sub autoreduce_leef { my ($self, %options) = @_; if ($self->{snmp_params}->{Retries} > 1) { $self->{snmp_params}->{Retries} = 1; $self->connect(); } return 1 if ($self->{subsetleef} == 1); $self->{subsetleef} = int($self->{subsetleef} / $self->{snmp_autoreduce_divisor}); $self->{subsetleef} = 1 if ($self->{subsetleef} < 1); my $array_ref = []; my $subset_current = 0; my $subset_construct = []; foreach ([@{$options{current}}], @{$self->{array_ref_ar}}) { foreach my $entry (@$_) {; push @$subset_construct, [$entry->[0], $entry->[1]]; $subset_current++; if ($subset_current == $self->{subsetleef}) { push @$array_ref, \@$subset_construct; $subset_construct = []; $subset_current = 0; } } } if ($subset_current) { push @$array_ref, \@$subset_construct; } $self->{array_ref_ar} = \@$array_ref; return 0; } sub get_leef_cache { my ($self, %options) = @_; my $results = {}; foreach my $oid (@{$options{oids}}) { if (defined($self->{snmp_cache}->{$oid})) { $results->{$oid} = $self->{snmp_cache}->{$oid}; } } if ($options{nothing_quit} == 1 && scalar(keys %$results) <= 0) { $self->{output}->add_option_msg(short_msg => 'SNMP GET Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } return $results; } sub get_leef { my ($self, %options) = @_; # $options{dont_quit} = integer # $options{nothing_quit} = integer # $options{oids} = ref to array of oids (example: ['.1.2', '.1.2']) # Returns array # 'undef' value for an OID means NoSuchValue my ($dont_quit) = (defined($options{dont_quit}) && $options{dont_quit} == 1) ? 1 : 0; my ($nothing_quit) = (defined($options{nothing_quit}) && $options{nothing_quit} == 1) ? 1 : 0; $self->set_error(); if (!defined($options{oids})) { if ($#{$self->{oids_loaded}} < 0) { if ($dont_quit == 1) { $self->set_error(error_status => -1, error_msg => "Need to specify OIDs"); return undef; } $self->{output}->add_option_msg(short_msg => 'Need to specify OIDs'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } push @{$options{oids}}, @{$self->{oids_loaded}}; @{$self->{oids_loaded}} = (); } if ($self->{use_snmp_cache} == 1) { return $self->get_leef_cache(oids => $options{oids}, nothing_quit => $nothing_quit); } my $results = {}; $self->{array_ref_ar} = []; my $subset_current = 0; my $subset_construct = []; foreach my $oid (@{$options{oids}}) { # Get last value next if ($oid !~ /(.*)\.(\d+)([\.\s]*)$/); my ($oid, $instance) = ($1, $2); $results->{$oid . "." . $instance} = undef; push @$subset_construct, [$oid, $instance]; $subset_current++; if ($subset_current == $self->{subsetleef}) { push @{$self->{array_ref_ar}}, \@$subset_construct; $subset_construct = []; $subset_current = 0; } } if ($subset_current) { push @{$self->{array_ref_ar}}, \@$subset_construct; } ############################ # If wrong oid with SNMP v1, packet resent (2 packets more). Not the case with SNMP > 1. # Can have "NoSuchName", if nothing works... # = v1: wrong oid # bless( [ # '.1.3.6.1.2.1.1.3', # '0', # '199720062', # 'TICKS' # ], 'SNMP::Varbind' ), # bless( [ # '.1.3.6.1.2.1.1.999', # '0' # ], 'SNMP::Varbind' ), # bless( [ # '.1.3.6.1.2.1.1', # '1000' # ], 'SNMP::Varbind' ) # > v1: wrong oid # bless( [ # '.1.3.6.1.2.1.1.3', # '0', # '199728713', # 'TICKS' # ], 'SNMP::Varbind' ), # bless( [ # '.1.3.6.1.2.1.1', # '3', # 'NOSUCHINSTANCE', # 'TICKS' # ], 'SNMP::Varbind' ) # bless( [ # '.1.3.6.1.2.1.1.999', # '0', # 'NOSUCHOBJECT', # 'NOSUCHOBJECT' # ], 'SNMP::Varbind' ), # bless( [ # '.1.3.6.1.2.1.1', # '1000', # 'NOSUCHOBJECT', # 'NOSUCHOBJECT' # ], 'SNMP::Varbind' ) ############################ my $total = 0; while (my $entry = shift(@{$self->{array_ref_ar}})) { my $vb = new SNMP::VarList(@{$entry}); $self->{session}->get($vb); if ($self->{session}->{ErrorNum}) { # 0 noError Pas d'erreurs. # 1 tooBig Reponse de taille trop grande. # 2 noSuchName Variable inexistante. # -24 Timeout if ($self->{session}->{ErrorNum} == 2) { # We are at the end with snmpv1. We next. next; } if ($self->{snmp_autoreduce} == 1 && ($self->{session}->{ErrorNum} == 1 || $self->{session}->{ErrorNum} == 5 || $self->{session}->{ErrorNum} == -24)) { next if ($self->autoreduce_leef(current => $entry) == 0); } my $msg = 'SNMP GET Request: ' . $self->{session}->{ErrorStr}; if ($dont_quit == 0) { $self->{output}->add_option_msg(short_msg => $msg); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->set_error(error_status => -1, error_msg => $msg); return undef; } # Some equipments gives a partial response and no error. # We look the last value if it's empty or not # In snmpv1 we have the retryNoSuch if (((scalar(@$vb) != scalar(@{$entry})) || (scalar(@{@$vb[-1]}) < 3)) && !$self->is_snmpv1()) { next if ($self->{snmp_autoreduce} == 1 && $self->autoreduce_leef(current => $entry) == 0); if ($dont_quit == 0) { $self->{output}->add_option_msg(short_msg => 'SNMP partial response. Please try --snmp-autoreduce option'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->set_error(error_status => -1, error_msg => 'SNMP partial response'); return undef; } foreach my $entry (@$vb) { if ($#$entry < 3) { # Can be snmpv1 not find next; } if (${$entry}[2] eq 'NOSUCHOBJECT' || ${$entry}[2] eq 'NOSUCHINSTANCE') { # Error in snmp > 1 next; } $total++; $results->{${$entry}[0] . "." . ${$entry}[1]} = ${$entry}[2]; } } if ($nothing_quit == 1 && $total == 0) { $self->{output}->add_option_msg(short_msg => 'SNMP GET Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->debug(results => $results) if ($self->{output}->is_debug()); return $results; } sub multiple_find_bigger { my ($self, %options) = @_; my $getting = {}; my @values = (); foreach my $key (keys %{$options{working_oids}}) { push @values, $options{working_oids}->{$key}->{start}; $getting->{ $options{working_oids}->{$key}->{start} } = $key; } @values = $self->oid_lex_sort(@values); return $getting->{pop(@values)}; } sub get_multiple_table_cache { my ($self, %options) = @_; my $results = {}; foreach my $entry (@{$options{oids}}) { my $result = $self->get_table_cache( oid => $entry->{oid}, start => $entry->{start}, end => $entry->{end}, nothing_quit => 0 ); if ($options{return_type} == 0) { $results->{ $entry->{oid} } = $result; } else { $results = { %$results, %$result }; } } my $total = 0; if ($options{nothing_quit} == 1) { if ($options{return_type} == 1) { $total = scalar(keys %$results); } else { foreach (keys %$results) { $total += scalar(keys %{$results->{$_}}); } } if ($total == 0) { $self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } } return $results; } sub get_multiple_table { my ($self, %options) = @_; # $options{dont_quit} = integer # $options{oids} = refs array # [ { oid => 'x.x.x.x', start => '', end => ''}, { oid => 'y.y.y.y', start => '', end => ''} ] # $options{return_type} = integer my ($return_type) = (defined($options{return_type}) && $options{return_type} == 1) ? 1 : 0; my ($dont_quit) = (defined($options{dont_quit}) && $options{dont_quit} == 1) ? 1 : 0; my ($nothing_quit) = (defined($options{nothing_quit}) && $options{nothing_quit} == 1) ? 1 : 0; $self->set_error(); if ($self->{use_snmp_cache} == 1) { return $self->get_multiple_table_cache( oids => $options{oids}, return_type => $return_type, nothing_quit => $nothing_quit ); } my $working_oids = {}; my $results = {}; # Check overlap foreach my $entry (@{$options{oids}}) { # Transform asking if ($entry->{oid} !~ /(.*)\.(\d+)([\.\s]*)$/) { if ($dont_quit == 1) { $self->set_error(error_status => -1, error_msg => "Method 'get_multiple_table': Wrong OID '" . $entry->{oid} . "'."); return undef; } $self->{output}->add_option_msg(short_msg => "Method 'get_multiple_table': Wrong OID '" . $entry->{oid} . "'."); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } if (defined($entry->{start})) { $working_oids->{$entry->{oid}} = { start => $entry->{start}, end => $entry->{end} }; # last in it } else { $working_oids->{$entry->{oid}} = { start => $entry->{oid}, end => $entry->{end} }; } if ($return_type == 0) { $results->{ $entry->{oid} } = {}; } } # we use a medium (UDP have a PDU limit. SNMP protcol cant send multiples for one request) # So we need to manage # It's for "bulk". We ask 50 next values. If you set 1, it's like a getnext (snmp v1) my $repeat_count = 50; if (defined($self->{maxrepetitions}) && $self->{maxrepetitions} =~ /^\d+$/) { $repeat_count = $self->{maxrepetitions}; } # Quit if base not the same or 'ENDOFMIBVIEW' value. Need all oid finish otherwise we continue :) while (1) { my $current_oids = 0; my @bindings = (); my @bases = (); foreach my $key (keys %{$working_oids}) { $working_oids->{$key}->{start} =~ /(.*)\.(\d+)([\.\s]*)$/; push @bindings, [$1, $2]; push @bases, $key; $current_oids++; last if ($current_oids > $self->{subsettable}); } # Nothing more to check. We quit last if ($current_oids == 0); my $vb = new SNMP::VarList(@bindings); if ($self->is_snmpv1() || defined($self->{snmp_force_getnext})) { $self->{session}->getnext($vb); } else { my $current_repeat_count = floor($repeat_count / $current_oids); $current_repeat_count = 1 if ($current_repeat_count == 0); $self->{session}->getbulk(0, $current_repeat_count, $vb); } # Error if ($self->{session}->{ErrorNum}) { # 0 noError Pas d'erreurs. # 1 tooBig Reponse de taille trop grande. # 2 noSuchName Variable inexistante. if ($self->{session}->{ErrorNum} == 2) { # We are at the end with snmpv1. Need to find the most up oid ;) my $oid_base = $self->multiple_find_bigger(working_oids => $working_oids); delete $working_oids->{$oid_base}; next; } if ($self->{snmp_autoreduce} == 1 && ($self->{session}->{ErrorNum} == 1 || $self->{session}->{ErrorNum} == 5 || $self->{session}->{ErrorNum} == -24)) { next if ($self->autoreduce_multiple_table(repeat_count => \$repeat_count) == 0); } my $msg = 'SNMP Table Request: ' . $self->{session}->{ErrorStr}; if ($dont_quit == 0) { $self->{output}->add_option_msg(short_msg => $msg); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->set_error(error_status => -1, error_msg => $msg); return undef; } # Manage # step by step: [ 1 => 1, 2 => 1, 3 => 1 ], [ 1 => 2, 2 => 2, 3 => 2 ],... my $pos = -1; foreach my $entry (@$vb) { $pos++; # Already destruct. we continue next if (!defined($working_oids->{ $bases[$pos % $current_oids] })); # ENDOFMIBVIEW is on each iteration. So we need to delete and skip after that if (${$entry}[2] eq 'ENDOFMIBVIEW') { delete $working_oids->{ $bases[$pos % $current_oids] }; # END mib next; } # Not in same table my $complete_oid = ${$entry}[0] . "." . ${$entry}[1]; my $base = $bases[$pos % $current_oids]; if ($complete_oid !~ /^$base\./ || (defined($working_oids->{ $bases[$pos % $current_oids] }->{end}) && $self->check_oid_up(current => $complete_oid, end => $working_oids->{ $bases[$pos % $current_oids] }->{end}))) { delete $working_oids->{ $bases[$pos % $current_oids] }; next; } if ($return_type == 0) { $results->{$bases[$pos % $current_oids]}->{$complete_oid} = ${$entry}[2]; } else { $results->{$complete_oid} = ${$entry}[2]; } $working_oids->{ $bases[$pos % $current_oids] }->{start} = $complete_oid; } # infinite loop. Some equipments it returns nothing!!?? if ($pos == -1) { $self->{output}->add_option_msg(short_msg => 'SNMP Table Request: problem to get values (try --snmp-force-getnext option)'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } } my $total = 0; if ($nothing_quit == 1) { if ($return_type == 1) { $total = scalar(keys %{$results}); } else { foreach (keys %{$results}) { $total += scalar(keys %{$results->{$_}}); } } if ($total == 0) { $self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } } $self->debug(results => $results) if ($self->{output}->is_debug()); return $results; } sub get_table_cache { my ($self, %options) = @_; my $branch = defined($options{start}) ? $options{start} : $options{oid}; my $results = {}; foreach my $oid ($self->oid_lex_sort(keys %{$self->{snmp_cache}})) { if ($oid =~ /^$branch\./) { $results->{$oid} = $self->{snmp_cache}->{$oid}; if (defined($options{end}) && $self->check_oid_up(current => $oid, end => $options{end})) { last; } } } if ($options{nothing_quit} == 1 && scalar(keys %$results) <= 0) { $self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } return $results; } sub get_table { my ($self, %options) = @_; # $options{dont_quit} = integer # $options{oid} = string (example: '.1.2') # $options{start} = string (example: '.1.2') # $options{end} = string (example: '.1.2') my ($dont_quit) = (defined($options{dont_quit}) && $options{dont_quit} == 1) ? 1 : 0; my ($nothing_quit) = (defined($options{nothing_quit}) && $options{nothing_quit} == 1) ? 1 : 0; $self->set_error(); if (defined($options{start})) { $options{start} = $self->clean_oid($options{start}); } if (defined($options{end})) { $options{end} = $self->clean_oid($options{end}); } if ($self->{use_snmp_cache} == 1) { return $self->get_table_cache( oid => $options{oid}, start => $options{start}, end => $options{end}, nothing_quit => $nothing_quit ); } # we use a medium (UDP have a PDU limit. SNMP protcol cant send multiples for one request) # So we need to manage # It's for "bulk". We ask 50 next values. If you set 1, it's like a getnext (snmp v1) my $repeat_count = 50; if (defined($self->{maxrepetitions}) && $self->{maxrepetitions} =~ /^\d+$/) { $repeat_count = $self->{maxrepetitions}; } # Transform asking if ($options{oid} !~ /(.*)\.(\d+)([\.\s]*)$/) { if ($dont_quit == 1) { $self->set_error(error_status => -1, error_msg => "Method 'get_table': Wrong OID '" . $options{oid} . "'."); return undef; } $self->{output}->add_option_msg(short_msg => "Method 'get_table': Wrong OID '" . $options{oid} . "'."); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } my $main_indice = $1 . '.' . $2; my $results = {}; # Quit if base not the same or 'ENDOFMIBVIEW' value my $leave = 1; my $last_oid; if (defined($options{start})) { $last_oid = $options{start}; } else { $last_oid = $options{oid}; } while ($leave) { $last_oid =~ /(.*)\.(\d+)([\.\s]*)$/; my $vb = new SNMP::VarList([$1, $2]); if ($self->is_snmpv1() || defined($self->{snmp_force_getnext})) { $self->{session}->getnext($vb); } else { $self->{session}->getbulk(0, $repeat_count, $vb); } # Error if ($self->{session}->{ErrorNum}) { # 0 noError Pas d'erreurs. # 1 tooBig Reponse de taille trop grande. # 2 noSuchName Variable inexistante. # -24 Timeout if ($self->{session}->{ErrorNum} == 2) { # We are at the end with snmpv1. We quit. last; } if ($self->{snmp_autoreduce} == 1 && ($self->{session}->{ErrorNum} == 1 || $self->{session}->{ErrorNum} == 5 || $self->{session}->{ErrorNum} == -24)) { next if ($self->autoreduce_table(repeat_count => \$repeat_count) == 0); } my $msg = 'SNMP Table Request: ' . $self->{session}->{ErrorStr}; if ($dont_quit == 0) { $self->{output}->add_option_msg(short_msg => $msg); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->set_error(error_status => -1, error_msg => $msg); return undef; } # Manage foreach my $entry (@$vb) { if (${$entry}[2] eq 'ENDOFMIBVIEW') { # END mib $leave = 0; last; } # Not in same table my $complete_oid = ${$entry}[0] . "." . ${$entry}[1]; if ($complete_oid !~ /^$main_indice\./ || (defined($options{end}) && $self->check_oid_up(current => $complete_oid, end => $options{end}))) { $leave = 0; last; } $results->{$complete_oid} = ${$entry}[2]; $last_oid = $complete_oid; } } if ($nothing_quit == 1 && scalar(keys %$results) == 0) { $self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.'); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->debug(results => $results) if ($self->{output}->is_debug()); return $results; } sub set { my ($self, %options) = @_; # $options{dont_quit} = integer # $options{oids} = ref to hash table my ($dont_quit) = (defined($options{dont_quit}) && $options{dont_quit} == 1) ? 1 : 0; $self->set_error(); my $vars = []; foreach my $oid (keys %{$options{oids}}) { # Get last value next if ($oid !~ /(.*)\.(\d+)([\.\s]*)$/); my $value = $options{oids}->{$oid}->{value}; my $type = $options{oids}->{$oid}->{type}; my ($oid, $instance) = ($1, $2); push @$vars, [$oid, $instance, $value, $type]; } $self->{session}->set($vars); if ($self->{session}->{ErrorNum}) { # 0 noError Pas d'erreurs. # 1 tooBig Reponse de taille trop grande. # 2 noSuchName Variable inexistante. my $msg = 'SNMP SET Request: ' . $self->{session}->{ErrorStr}; if ($dont_quit == 0) { $self->{output}->add_option_msg(short_msg => $msg); $self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit}); } $self->set_error(error_status => -1, error_msg => $msg); return undef; } return 0; } sub is_snmpv1 { my ($self) = @_; if ($self->{snmp_params}->{Version} eq '1') { return 1; } return 0; } sub clean_oid { my ($self, $oid) = @_; $oid =~ s/\.$//; $oid =~ s/^(\d)/\.$1/; return $oid; } sub check_oid_up { my ($self, %options) = @_; my $current_oid = $options{current}; my $end_oid = $options{end}; my @current_oid_splitted = split /\./, $current_oid; my @end_oid_splitted = split /\./, $end_oid; # Skip first value (before first '.' empty) for (my $i = 1; $i <= $#current_oid_splitted && $i <= $#end_oid_splitted; $i++) { if (int($current_oid_splitted[$i]) > int($end_oid_splitted[$i])) { return 1; } } return 0; } sub check_options { my ($self, %options) = @_; $self->{snmp_errors_exit} = $options{option_results}->{snmp_errors_exit}; if (defined($options{option_results}->{snmp_cache_file}) && $options{option_results}->{snmp_cache_file} ne '') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'JSON::XS', error_msg => "Cannot load module 'JSON::XS'." ); my $content = centreon::plugins::misc::slurp_file(output => $self->{output}, file => $options{option_results}->{snmp_cache_file}); eval { $self->{snmp_cache} = JSON::XS->new->decode($content); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode json cache file: $@"); $self->{output}->option_exit(); } $self->{use_snmp_cache} = 1; return ; } if (!defined($options{option_results}->{host})) { $self->{output}->add_option_msg(short_msg => 'Missing parameter --hostname.'); $self->{output}->option_exit(); } $options{option_results}->{snmp_version} =~ s/^v//; if ($options{option_results}->{snmp_version} !~ /1|2c|2|3/) { $self->{output}->add_option_msg(short_msg => 'Unknown snmp version.'); $self->{output}->option_exit(); } $self->{snmp_force_getnext} = $options{option_results}->{snmp_force_getnext}; $self->{maxrepetitions} = $options{option_results}->{maxrepetitions}; $self->{subsetleef} = (defined($options{option_results}->{subsetleef}) && $options{option_results}->{subsetleef} =~ /^[0-9]+$/) ? $options{option_results}->{subsetleef} : 50; $self->{subsettable} = (defined($options{option_results}->{subsettable}) && $options{option_results}->{subsettable} =~ /^[0-9]+$/) ? $options{option_results}->{subsettable} : 100; $self->{snmp_autoreduce} = 0; $self->{snmp_autoreduce_divisor} = 2; if (defined($options{option_results}->{snmp_autoreduce})) { $self->{snmp_autoreduce} = 1; $self->{snmp_autoreduce_divisor} = $1 if ($options{option_results}->{snmp_autoreduce} =~ /(\d+(\.\d+)?)/ && $1 > 1); } %{$self->{snmp_params}} = ( DestHost => $options{option_results}->{host}, Community => $options{option_results}->{snmp_community}, Version => $options{option_results}->{snmp_version}, RemotePort => $options{option_results}->{snmp_port}, Retries => 5 ); if (defined($options{option_results}->{snmp_timeout}) && $options{option_results}->{snmp_timeout} =~ /^[0-9]+$/) { $self->{snmp_params}->{Timeout} = $options{option_results}->{snmp_timeout} * (10**6); } if (defined($options{option_results}->{snmp_retries}) && $options{option_results}->{snmp_retries} =~ /^[0-9]+$/) { $self->{snmp_params}->{Retries} = $options{option_results}->{snmp_retries}; } if ($options{option_results}->{snmp_version} eq '3') { delete $self->{snmp_params}->{Community}; $self->{snmp_params}->{Context} = $options{option_results}->{snmp_context_name} if (defined($options{option_results}->{snmp_context_name})); $self->{snmp_params}->{ContextEngineId} = $options{option_results}->{snmp_context_engine_id} if (defined($options{option_results}->{snmp_context_engine_id})); $self->{snmp_params}->{SecEngineId} = $options{option_results}->{snmp_security_engine_id} if (defined($options{option_results}->{snmp_security_engine_id})); $self->{snmp_params}->{SecName} = $options{option_results}->{snmp_security_name} if (defined($options{option_results}->{snmp_security_name})); # Certificate SNMPv3. Need net-snmp > 5.6 if (defined($options{option_results}->{snmp_tls_transport}) && $options{option_results}->{snmp_tls_transport} =~ /^dtlsudp|tlstcp$/) { $self->{snmp_params}->{DestHost} = $options{option_results}->{snmp_tls_transport} . ':' . $options{option_results}->{host}; $self->{snmp_params}->{OurIdentity} = $options{option_results}->{snmp_tls_our_identity} if (defined($options{option_results}->{snmp_tls_our_identity})); $self->{snmp_params}->{TheirIdentity} = $options{option_results}->{snmp_tls_their_identity} if (defined($options{option_results}->{snmp_tls_their_identity})); $self->{snmp_params}->{TheirHostname} = $options{option_results}->{snmp_tls_their_hostname} if (defined($options{option_results}->{snmp_tls_their_hostname})); $self->{snmp_params}->{TrustCert} = $options{option_results}->{snmp_tls_trust_cert} if (defined($options{option_results}->{snmp_tls_trust_cert})); $self->{snmp_params}->{SecLevel} = 'authPriv'; return ; } if (!defined($options{option_results}->{snmp_security_name}) || $options{option_results}->{snmp_security_name} eq '') { $self->{output}->add_option_msg(short_msg => 'Missing parameter Security Name.'); $self->{output}->option_exit(); } # unauthenticated and unencrypted $self->{snmp_params}->{SecLevel} = 'noAuthNoPriv'; my $user_activate = 0; if (defined($options{option_results}->{snmp_auth_passphrase}) && $options{option_results}->{snmp_auth_passphrase} ne '') { if (!defined($options{option_results}->{snmp_auth_protocol})) { $self->{output}->add_option_msg(short_msg => 'Missing parameter authenticate protocol.'); $self->{output}->option_exit(); } $options{option_results}->{snmp_auth_protocol} = uc($options{option_results}->{snmp_auth_protocol}); if ($options{option_results}->{snmp_auth_protocol} !~ /^(?:MD5|SHA|SHA224|SHA256|SHA384|SHA512)$/) { $self->{output}->add_option_msg(short_msg => 'Wrong authentication protocol.'); $self->{output}->option_exit(); } $self->{snmp_params}->{SecLevel} = 'authNoPriv'; $self->{snmp_params}->{AuthProto} = $options{option_results}->{snmp_auth_protocol}; $self->{snmp_params}->{AuthPass} = $options{option_results}->{snmp_auth_passphrase}; $user_activate = 1; } if (defined($options{option_results}->{snmp_priv_passphrase}) && $options{option_results}->{snmp_priv_passphrase} ne '') { if (!defined($options{option_results}->{snmp_priv_protocol})) { $self->{output}->add_option_msg(short_msg => 'Missing parameter privacy protocol.'); $self->{output}->option_exit(); } $options{option_results}->{snmp_priv_protocol} = uc($options{option_results}->{snmp_priv_protocol}); if ($options{option_results}->{snmp_priv_protocol} !~ /^(?:DES|AES|AES192|AES192C|AES256|AES256C)$/) { $self->{output}->add_option_msg(short_msg => 'Wrong privacy protocol.'); $self->{output}->option_exit(); } if ($user_activate == 0) { $self->{output}->add_option_msg(short_msg => 'Cannot use snmp v3 privacy option without snmp v3 authentification options.'); $self->{output}->option_exit(); } $self->{snmp_params}->{SecLevel} = 'authPriv'; $self->{snmp_params}->{PrivPass} = $options{option_results}->{snmp_priv_passphrase}; $self->{snmp_params}->{PrivProto} = $options{option_results}->{snmp_priv_protocol}; } } } sub set_snmp_connect_params { my ($self, %options) = @_; foreach (keys %options) { $self->{snmp_params}->{$_} = $options{$_}; } } sub set_snmp_params { my ($self, %options) = @_; foreach (keys %options) { $self->{$_} = $options{$_}; } } sub set_error { my ($self, %options) = @_; # $options{error_msg} = string error # $options{error_status} = integer status $self->{error_status} = defined($options{error_status}) ? $options{error_status} : 0; $self->{error_msg} = defined($options{error_msg}) ? $options{error_msg} : undef; } sub error_status { my ($self) = @_; return $self->{error_status}; } sub error { my ($self) = @_; return $self->{error_msg}; } sub get_hostname { my ($self) = @_; my $host = $self->{snmp_params}->{DestHost}; $host =~ s/.*://; return $host; } sub get_port { my ($self) = @_; return $self->{snmp_params}->{RemotePort}; } sub map_instance { my ($self, %options) = @_; my $results = {}; my $instance = ''; $instance = '.' . $options{instance} if (defined($options{instance})); foreach my $name (keys %{$options{mapping}}) { my $entry = $options{mapping}->{$name}->{oid} . $instance; if (defined($options{results}->{$entry})) { $results->{$name} = $options{results}->{$entry}; } elsif (defined($options{results}->{$options{mapping}->{$name}->{oid}}->{$entry})) { $results->{$name} = $options{results}->{$options{mapping}->{$name}->{oid}}->{$entry}; } else { $results->{$name} = defined($options{default}) ? $options{default} : undef; } if (defined($options{mapping}->{$name}->{map})) { if (defined($results->{$name})) { $results->{$name} = defined($options{mapping}->{$name}->{map}->{$results->{$name}}) ? $options{mapping}->{$name}->{map}->{$results->{$name}} : (defined($options{default}) ? $options{default} : 'unknown'); } } } return $results; } sub debug { my ($self, %options) = @_; foreach my $oid1 ($self->oid_lex_sort(keys %{$options{results}})) { if (ref($options{results}->{$oid1}) eq 'HASH') { foreach my $oid2 ($self->oid_lex_sort(keys %{$options{results}->{$oid1}})) { $self->{output}->output_add(long_msg => $oid2 . ' = ' . (defined($options{results}->{$oid1}->{$oid2}) ? $options{results}->{$oid1}->{$oid2} : 'undef'), debug => 1); } } else { $self->{output}->output_add(long_msg => $oid1 . ' = ' . (defined($options{results}->{$oid1}) ? $options{results}->{$oid1} : 'undef'), debug => 1); } } } sub oid_lex_sort { my $self = shift; if (@_ <= 1) { return @_; } return map { $_->[0] } sort { $a->[1] cmp $b->[1] } map { my $oid = $_; $oid =~ s/^\.//; $oid =~ s/ /\.0/g; [$_, pack 'N*', split m/\./, $oid] } @_; } 1; =head1 NAME SNMP global =head1 SYNOPSIS snmp class =head1 SNMP OPTIONS =over 8 =item B<--hostname> Name or address of the host to monitor (mandatory). =item B<--snmp-community> SNMP community (default value: public). It is recommended to use a read-only community. =item B<--snmp-version> Version of the SNMP protocol. 1 for SNMP v1 (default), 2 for SNMP v2c, 3 for SNMP v3. =item B<--snmp-port> UDP port to send the SNMP request to (default: 161). =item B<--snmp-timeout> Time to wait before sending the request again if no reply has been received, in seconds (default: 1). See also --snmp-retries. =item B<--snmp-retries> Maximum number of retries (default: 5). =item B<--maxrepetitions> Max repetitions value (default: 50) (only for SNMP v2 and v3). =item B<--subsetleef> How many OID values per SNMP request (default: 50) (for get_leef method. Be cautious when you set it. Prefer to let the default value). =item B<--snmp-autoreduce> Progressively reduce the number of requested OIDs in bulk mode. Use it in case of SNMP errors (by default, the number is divided by 2). =item B<--snmp-force-getnext> Use SNMP getnext function in SNMP v2c and v3. This will request one OID at a time. =item B<--snmp-cache-file> Use SNMP cache file. =item B<--snmp-username> SNMP v3 only: User name (securityName). =item B<--authpassphrase> SNMP v3 only: Pass phrase hashed using the authentication protocol defined in the --authprotocol option. =item B<--authprotocol> SNMP v3 only: Authentication protocol: MD5|SHA. Since net-snmp 5.9.1: SHA224|SHA256|SHA384|SHA512. =item B<--privpassphrase> SNMP v3 only: Privacy pass phrase (privPassword) to encrypt messages using the protocol defined in the --privprotocol option. =item B<--privprotocol> SNMP v3 only: Privacy protocol (privProtocol) used to encrypt messages. Supported protocols are: DES|AES and since net-snmp 5.9.1: AES192|AES192C|AES256|AES256C. =item B<--contextname> SNMP v3 only: Context name (contextName), if relevant for the monitored host. =item B<--contextengineid> SNMP v3 only: Context engine ID (contextEngineID), if relevant for the monitored host, given as a hexadecimal string. =item B<--securityengineid> SNMP v3 only: Security engine ID, given as a hexadecimal string. =item B<--snmp-errors-exit> Expected status in case of SNMP error or timeout. Possible values are warning, critical and unknown (default). =item B<--snmp-tls-transport> Transport protocol for TLS communication (can be: 'dtlsudp', 'tlstcp'). =item B<--snmp-tls-our-identity> X.509 certificate to identify ourselves. Can be the path to the certificate file or its contents. =item B<--snmp-tls-their-identity> X.509 certificate to identify the remote host. Can be the path to the certificate file or its contents. This option is unnecessary if the certificate is already trusted by your system. =item B<--snmp-tls-their-hostname> Common Name (CN) expected in the certificate sent by the host if it differs from the value of the --hostname parameter. =item B<--snmp-tls-trust-cert> A trusted CA certificate used to verify a remote host's certificate. If you use this option, you must also define --snmp-tls-their-hostname. =back =head1 DESCRIPTION B<snmp>. =cut CENTREON_PLUGINS_SNMP $fatpacked{"centreon/plugins/ssh.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_SSH'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::ssh; use strict; use warnings; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{noptions}) || $options{noptions} != 1) { $options{options}->add_options(arguments => { 'ssh-backend:s' => { name => 'ssh_backend', default => 'sshcli' }, 'ssh-port:s' => { name => 'ssh_port' }, 'ssh-priv-key:s' => { name => 'ssh_priv_key' }, 'ssh-username:s' => { name => 'ssh_username' }, 'ssh-password:s' => { name => 'ssh_password' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'SSH GLOBAL OPTIONS'); } centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'centreon::plugins::backend::ssh::sshcli', error_msg => "Cannot load module 'centreon::plugins::backend::ssh::sshcli'." ); $self->{backend_sshcli} = centreon::plugins::backend::ssh::sshcli->new(%options); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'centreon::plugins::backend::ssh::plink', error_msg => "Cannot load module 'centreon::plugins::backend::ssh::plink'." ); $self->{backend_plink} = centreon::plugins::backend::ssh::plink->new(%options); centreon::plugins::misc::mymodule_load( output => $options{output}, module => 'centreon::plugins::backend::ssh::libssh', error_msg => "Cannot load module 'centreon::plugins::backend::ssh::libssh'." ); $self->{backend_libssh} = centreon::plugins::backend::ssh::libssh->new(%options); $self->{output} = $options{output}; return $self; } sub check_options { my ($self, %options) = @_; $self->{ssh_backend} = $options{option_results}->{ssh_backend}; $self->{ssh_port} = defined($options{option_results}->{ssh_port}) && $options{option_results}->{ssh_port} =~ /(\d+)/ ? $1 : 22; $self->{ssh_backend} = 'sshcli' if (!defined($options{option_results}->{ssh_backend}) || $options{option_results}->{ssh_backend} eq ''); if (!defined($self->{'backend_' . $self->{ssh_backend}})) { $self->{output}->add_option_msg(short_msg => 'unknown ssh backend: ' . $self->{ssh_backend}); $self->{output}->option_exit(); } $self->{'backend_' . $self->{ssh_backend}}->check_options(%options); } sub get_port { my ($self, %options) = @_; return $self->{ssh_port}; } sub execute { my ($self, %options) = @_; return $self->{'backend_' . $self->{ssh_backend}}->execute(%options); } 1; =head1 NAME SSH abstraction layer. =head1 SYNOPSIS SSH abstraction layer for sscli, plink and libssh backends =head1 SSH GLOBAL OPTIONS =over 8 =item B<--ssh-backend> Define the backend you want to use. It can be: sshcli (default), plink and libssh. =item B<--ssh-username> Define the user name to log in to the host. =item B<--ssh-password> Define the password associated with the user name. Cannot be used with the sshcli backend. Warning: using a password is not recommended. Use --ssh-priv-key instead. =item B<--ssh-port> Define the TCP port on which SSH is listening. =item B<--ssh-priv-key> Define the private key file to use for user authentication. =back =head1 DESCRIPTION B<ssh>. =cut CENTREON_PLUGINS_SSH $fatpacked{"centreon/plugins/statefile.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_STATEFILE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::statefile; use strict; use warnings; use Data::Dumper; use centreon::plugins::misc; my $default_dir = '/var/lib/centreon/centplugins'; if ($^O eq 'MSWin32') { $default_dir = 'C:/Windows/Temp'; } sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (defined($options{options})) { $options{options}->add_options(arguments => { 'memcached:s' => { name => 'memcached' }, 'redis-server:s' => { name => 'redis_server' }, 'redis-attribute:s%' => { name => 'redis_attribute' }, 'redis-db:s' => { name => 'redis_db' }, 'memexpiration:s' => { name => 'memexpiration', default => 86400 }, 'statefile-dir:s' => { name => 'statefile_dir', default => $default_dir }, 'statefile-suffix:s' => { name => 'statefile_suffix', default => '' }, 'statefile-concat-cwd' => { name => 'statefile_concat_cwd' }, 'statefile-storable' => { name => 'statefile_storable' }, # legacy 'failback-file' => { name => 'failback_file' }, 'statefile-format:s' => { name => 'statefile_format' }, 'statefile-key:s' => { name => 'statefile_key' }, 'statefile-cipher:s' => { name => 'statefile_cipher' } }); $options{options}->add_help(package => __PACKAGE__, sections => 'RETENTION OPTIONS', once => 1); } $self->{error} = 0; $self->{output} = $options{output}; $self->{datas} = {}; $self->{storable} = 0; $self->{memcached_ok} = 0; $self->{memcached} = undef; $self->{statefile_dir} = undef; $self->{statefile_suffix} = undef; return $self; } sub check_options { my ($self, %options) = @_; if (defined($options{option_results}) && defined($options{option_results}->{memcached})) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Memcached::libmemcached', error_msg => "Cannot load module 'Memcached::libmemcached'." ); $self->{memcached} = Memcached::libmemcached->new(); Memcached::libmemcached::memcached_server_add($self->{memcached}, $options{option_results}->{memcached}); } # Check redis if (defined($options{option_results}->{redis_server})) { $self->{redis_attributes} = ''; if (defined($options{option_results}->{redis_attribute})) { foreach (keys %{$options{option_results}->{redis_attribute}}) { $self->{redis_attributes} .= "$_ => " . $options{option_results}->{redis_attribute}->{$_} . ', '; } } centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Redis', error_msg => "Cannot load module 'Redis'." ); eval { $options{option_results}->{redis_server} .= ':6379' if ($options{option_results}->{redis_server} !~ /:\d+$/); $self->{redis_cnx} = Redis->new( server => $options{option_results}->{redis_server}, eval $self->{redis_attributes} ); if (defined($self->{redis_cnx}) && defined($options{option_results}->{redis_db}) && $options{option_results}->{redis_db} ne '' ) { $self->{redis_cnx}->select($options{option_results}->{redis_db}); } }; if (!defined($self->{redis_cnx}) && !defined($options{option_results}->{failback_file})) { $self->{output}->add_option_msg(short_msg => "redis connection issue: $@"); $self->{output}->option_exit(); } } $self->{statefile_format} = 'json'; if (defined($options{option_results}->{statefile_format}) && $options{option_results}->{statefile_format} ne '' && $options{option_results}->{statefile_format} =~ /^(?:dumper|json|storable)$/) { $self->{statefile_format} = $options{option_results}->{statefile_format}; } elsif (defined($options{default_format}) && $options{default_format} =~ /^(?:dumper|json|storable)$/) { $self->{statefile_format} = $options{default_format}; } if (defined($options{option_results}->{statefile_storable})) { $self->{statefile_format} = 'storable'; } if ($self->{statefile_format} eq 'dumper') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Safe', no_quit => 1 ); $self->{safe} = Safe->new(); $self->{safe}->share('$datas'); } elsif ($self->{statefile_format} eq 'storable') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Storable', error_msg => "Cannot load module 'Storable'." ); } elsif ($self->{statefile_format} eq 'json') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'JSON::XS', error_msg => "Cannot load module 'JSON::XS'." ); } $self->{statefile_cipher} = defined($options{option_results}->{statefile_cipher}) && $options{option_results}->{statefile_cipher} ne '' ? $options{option_results}->{statefile_cipher} : 'AES'; $self->{statefile_key} = defined($options{option_results}->{statefile_key}) && $options{option_results}->{statefile_key} ne '' ? $options{option_results}->{statefile_key} : ''; if ($self->{statefile_key} ne '') { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Crypt::Mode::CBC', error_msg => "Cannot load module 'Crypt::Mode::CBC'." ); centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Crypt::PRNG', error_msg => "Cannot load module 'Crypt::PRNG'." ); centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'MIME::Base64', error_msg => "Cannot load module 'MIME::Base64'." ); } $self->{statefile_dir} = $options{option_results}->{statefile_dir}; if ($self->{statefile_dir} ne $default_dir && defined($options{option_results}->{statefile_concat_cwd})) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Cwd', error_msg => "Cannot load module 'Cwd'." ); $self->{statefile_dir} = Cwd::cwd() . '/' . $self->{statefile_dir}; } $self->{statefile_suffix} = $options{option_results}->{statefile_suffix}; $self->{memexpiration} = $options{option_results}->{memexpiration}; } sub error { my ($self) = shift; if (@_) { $self->{error} = $_[0]; } return $self->{error}; } sub get_key { my ($self, %options) = @_; my $key = $options{key}; { use bytes; my $size = length($key); my $minsize = Crypt::Cipher->min_keysize($options{cipher}); if ($minsize > $size) { $key .= "0" x ($minsize - $size); } } return $key; } sub decrypt { my ($self, %options) = @_; return (1, $options{data}) if (!defined($options{data}->{encrypted})); my $plaintext; eval { my $cipher = Crypt::Mode::CBC->new($options{data}->{cipher}, 1); $plaintext = $cipher->decrypt( MIME::Base64::decode_base64($options{data}->{ciphertext}), $self->get_key(key => $self->{statefile_key}, cipher => $options{data}->{cipher}), pack('H*', $options{data}->{iv}) ); }; if ($@) { return 0; } return $self->deserialize(data => $plaintext, nocipher => 1); } sub deserialize { my ($self, %options) = @_; my $deserialized = ''; if ($self->{statefile_format} eq 'dumper') { our $datas; $self->{safe}->reval($options{data}, 1); return 0 if ($@); $deserialized = $datas; } elsif ($self->{statefile_format} eq 'storable') { eval { $deserialized = Storable::thaw($options{data}); }; return 0 if ($@); } elsif ($self->{statefile_format} eq 'json') { eval { $deserialized = JSON::XS->new->decode($options{data}); }; return 0 if ($@); } return 0 if (!defined($deserialized) || ref($deserialized) ne 'HASH'); my $rv = 1; if ($self->{statefile_key} ne '' && !defined($options{nocipher})) { ($rv, $deserialized) = $self->decrypt(data => $deserialized); } return ($rv, $deserialized); } sub slurp { my ($self, %options) = @_; my $content = do { local $/ = undef; if (!open my $fh, '<', $options{file}) { $self->{output}->add_option_msg(short_msg => "Could not open file $options{file}: $!"); $self->{output}->option_exit(); } <$fh>; }; return $content; } sub read { my ($self, %options) = @_; $self->{statefile_suffix} = defined($options{statefile_suffix}) ? $options{statefile_suffix} : $self->{statefile_suffix}; $self->{statefile_dir} = defined($options{statefile_dir}) ? $options{statefile_dir} : $self->{statefile_dir}; $self->{statefile} = defined($options{statefile}) ? $options{statefile} . $self->{statefile_suffix} : $self->{statefile}; $self->{no_quit} = defined($options{no_quit}) && $options{no_quit} == 1 ? 1 : 0; my ($data, $rv); if (defined($self->{memcached})) { # if "SUCCESS" or "NOT FOUND" is ok. Other with use the file my $val = Memcached::libmemcached::memcached_get($self->{memcached}, $self->{statefile_dir} . '/' . $self->{statefile}); if (defined($self->{memcached}->errstr) && $self->{memcached}->errstr =~ /^SUCCESS|NOT FOUND$/i) { $self->{memcached_ok} = 1; if (defined($val)) { ($rv, $data) = $self->deserialize(data => $val); $self->{datas} = defined($data) ? $data : {}; return $rv; } return 0; } } if (defined($self->{redis_cnx})) { my $val = $self->{redis_cnx}->get($self->{statefile_dir} . "/" . $self->{statefile}); if (defined($val)) { ($rv, $data) = $self->deserialize(data => $val); $self->{datas} = defined($data) ? $data : {}; return $rv; } return 0; } if (! -e $self->{statefile_dir} . '/' . $self->{statefile}) { if (! -w $self->{statefile_dir} || ! -x $self->{statefile_dir}) { $self->error(1); $self->{output}->add_option_msg(short_msg => "Cannot write statefile '" . $self->{statefile_dir} . "/" . $self->{statefile} . "'. Need write/exec permissions on directory."); if ($self->{no_quit} == 0) { $self->{output}->option_exit(); } } return 0; } elsif (! -w $self->{statefile_dir} . '/' . $self->{statefile}) { $self->error(1); $self->{output}->add_option_msg(short_msg => "Cannot write statefile '" . $self->{statefile_dir} . "/" . $self->{statefile} . "'. Need write permissions on file."); if ($self->{no_quit} == 0) { $self->{output}->option_exit(); } return 1; } elsif (! -s $self->{statefile_dir} . '/' . $self->{statefile}) { # Empty file. Not a problem. Maybe plugin not manage not values return 0; } $data = $self->slurp(file => $self->{statefile_dir} . '/' . $self->{statefile}); ($rv, $data) = $self->deserialize(data => $data); $self->{datas} = defined($data) ? $data : {}; return $rv; } sub get_string_content { my ($self, %options) = @_; return Data::Dumper::Dumper($self->{datas}); } sub get { my ($self, %options) = @_; if (defined($self->{datas}->{ $options{name} })) { return $self->{datas}->{ $options{name} }; } return undef; } sub encrypt { my ($self, %options) = @_; my $data = { encrypted => 1, cipher => $self->{statefile_cipher}, iv => Crypt::PRNG::random_bytes_hex(16) }; eval { my $cipher = Crypt::Mode::CBC->new($self->{statefile_cipher}, 1); $data->{ciphertext} = MIME::Base64::encode_base64( $cipher->encrypt( $options{data}, $self->get_key(key => $self->{statefile_key}, cipher => $self->{statefile_cipher}), pack('H*', $data->{iv}) ), '' ); }; if ($@) { $self->{output}->add_option_msg(short_msg => "cipher encrypt error: $@"); $self->{output}->option_exit(); } return $self->serialize(data => $data, nocipher => 1); } sub serialize { my ($self, %options) = @_; my $serialized = ''; if ($self->{statefile_format} eq 'dumper') { $serialized = Data::Dumper->Dump([$options{data}], ['datas']); } elsif ($self->{statefile_format} eq 'storable') { $serialized = Storable::freeze($options{data}); } elsif ($self->{statefile_format} eq 'json') { eval { $serialized = JSON::XS->new->encode($options{data}); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot serialize statefile '" . $self->{statefile_dir} . "/" . $self->{statefile} . "'"); $self->{output}->option_exit(); } } if ($self->{statefile_key} ne '' && !defined($options{nocipher})) { $serialized = $self->encrypt(data => $serialized); } return $serialized; } sub write { my ($self, %options) = @_; my $serialized = $self->serialize(data => $options{data}); if ($self->{memcached_ok} == 1) { Memcached::libmemcached::memcached_set( $self->{memcached}, $self->{statefile_dir} . '/' . $self->{statefile}, $serialized, $self->{memexpiration} ); if (defined($self->{memcached}->errstr) && $self->{memcached}->errstr =~ /^SUCCESS$/i) { return ; } } if (defined($self->{redis_cnx})) { return if (defined($self->{redis_cnx}->set( $self->{statefile_dir} . '/' . $self->{statefile}, $serialized, 'EX', $self->{memexpiration})) ); } open FILE, '>', $self->{statefile_dir} . '/' . $self->{statefile}; print FILE $serialized; close FILE; } 1; =head1 NAME Statefile class =head1 SYNOPSIS - =head1 RETENTION OPTIONS =over 8 =item B<--memcached> Memcached server to use (only one server). =item B<--redis-server> Redis server to use (only one server). Syntax: address[:port] =item B<--redis-attribute> Set Redis Options (--redis-attribute="cnx_timeout=5"). =item B<--redis-db> Set Redis database index. =item B<--failback-file> Failback on a local file if Redis connection fails. =item B<--memexpiration> Time to keep data in seconds (default: 86400). =item B<--statefile-dir> Define the cache directory (default: '/var/lib/centreon/centplugins'). =item B<--statefile-suffix> Define a suffix to customize the statefile name (default: ''). =item B<--statefile-concat-cwd> If used with the '--statefile-dir' option, the latter's value will be used as a sub-directory of the current working directory. Useful on Windows when the plugin is compiled, as the file system and permissions are different from Linux. =item B<--statefile-format> Define the format used to store the cache. Available formats: 'dumper', 'storable', 'json' (default). =item B<--statefile-key> Define the key to encrypt/decrypt the cache. =item B<--statefile-cipher> Define the cipher algorithm to encrypt the cache (default: 'AES'). =back =head1 DESCRIPTION B<statefile>. =cut CENTREON_PLUGINS_STATEFILE $fatpacked{"centreon/plugins/templates/catalog_functions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_TEMPLATES_CATALOG_FUNCTIONS'; # # Copyright 2018 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::templates::catalog_functions; use strict; use warnings; use Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(catalog_status_threshold catalog_status_threshold_ng catalog_status_calc); sub catalog_status_threshold { my ($self, %options) = @_; my $status = 'ok'; my $label = $self->{label}; $label =~ s/-/_/g; if (defined($self->{instance_mode}->{option_results}->{'ok_' . $label}) && $self->{instance_mode}->{option_results}->{'ok_' . $label} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'ok_' . $label})) { $status = 'ok'; } elsif (defined($self->{instance_mode}->{option_results}->{'critical_' . $label}) && $self->{instance_mode}->{option_results}->{'critical_' . $label} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'critical_' . $label})) { $status = 'critical'; } elsif (defined($self->{instance_mode}->{option_results}->{'warning_' . $label}) && $self->{instance_mode}->{option_results}->{'warning_' . $label} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'warning_' . $label})) { $status = 'warning'; } elsif (defined($self->{instance_mode}->{option_results}->{'unknown_' . $label}) && $self->{instance_mode}->{option_results}->{'unknown_' . $label} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'unknown_' . $label})) { $status = 'unknown'; } return $status; } sub catalog_status_threshold_ng { my ($self, %options) = @_; my $status = 'ok'; my $message; if (defined($self->{instance_mode}->{option_results}->{'critical-' . $self->{label}}) && $self->{instance_mode}->{option_results}->{'critical-' . $self->{label}} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'critical-' . $self->{label}})) { $status = 'critical'; } elsif (defined($self->{instance_mode}->{option_results}->{'warning-' . $self->{label}}) && $self->{instance_mode}->{option_results}->{'warning-' . $self->{label}} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'warning-' . $self->{label}})) { $status = 'warning'; } elsif (defined($self->{instance_mode}->{option_results}->{'unknown-' . $self->{label}}) && $self->{instance_mode}->{option_results}->{'unknown-' . $self->{label}} ne '' && $self->eval(value => $self->{instance_mode}->{option_results}->{'unknown-' . $self->{label}})) { $status = 'unknown'; } return $status; } sub catalog_status_calc { my ($self, %options) = @_; foreach (keys %{$options{new_datas}}) { if (/^\Q$self->{instance}\E_(.*)/) { $self->{result_values}->{$1} = $options{new_datas}->{$_}; } } } 1; CENTREON_PLUGINS_TEMPLATES_CATALOG_FUNCTIONS $fatpacked{"centreon/plugins/templates/counter.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_TEMPLATES_COUNTER'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::templates::counter; use base qw(centreon::plugins::mode); use strict; use warnings; use centreon::plugins::values; use centreon::plugins::misc; use JSON::XS; my $sort_subs = { num => sub { $a <=> $b }, cmp => sub { $a cmp $b }, }; sub set_counters { my ($self, %options) = @_; if (!defined($self->{maps_counters})) { $self->{maps_counters} = {}; } $self->{maps_counters_type} = []; # 0 = mode total # 1 = mode instances #push @{$self->{maps_counters_type}}, { # name => 'global', type => 0, message_separator => ', ', cb_prefix_output => undef, cb_init => undef, #}; #$self->{maps_counters}->{global} = [ # { label => 'client', set => { # key_values => [ { name => 'client' } ], # output_template => 'Current client connections : %s', # perfdatas => [ # { label => 'Client', value => 'client', template => '%s', # min => 0, unit => 'con' }, # ], # } # }, #]; # Example for instances #push @{$self->{maps_counters_type}}, { # name => 'cpu', type => 1, message_separator => ', ', cb_prefix_output => undef, cb_init => undef, # message_multiple => 'All CPU usages are ok', #}; } sub get_callback { my ($self, %options) = @_; if (defined($options{method_name})) { return $self->can($options{method_name}); } return undef; } sub call_object_callback { my ($self, %options) = @_; if (defined($options{method_name})) { my $method = $self->can($options{method_name}); if ($method) { return $self->$method(%options); } } return undef; } sub get_threshold_prefix { my ($self, %options) = @_; my $prefix = ''; END_LOOP: foreach (@{$self->{maps_counters_type}}) { if ($_->{name} eq $options{name}) { $prefix = 'instance-' if ($_->{type} == 1); last; } if ($_->{type} == 3) { foreach (@{$_->{group}}) { if ($_->{name} eq $options{name}) { $prefix = 'instance-' if ($_->{type} == 0); $prefix = 'subinstance-' if ($_->{type} == 1); last END_LOOP; } } } } return $prefix; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $self->{version} = '1.0'; $options{options}->add_options(arguments => { 'filter-counters-block:s' => { name => 'filter_counters_block' }, 'filter-counters:s' => { name => 'filter_counters' }, 'display-ok-counters:s' => { name => 'display_ok_counters' }, 'list-counters' => { name => 'list_counters' } }); $self->{statefile_value} = undef; if (defined($options{statefile}) && $options{statefile}) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'centreon::plugins::statefile', error_msg => "Cannot load module 'centreon::plugins::statefile'." ); $self->{statefile_value} = centreon::plugins::statefile->new(%options); } $self->{maps_counters} = {} if (!defined($self->{maps_counters})); $self->set_counters(%options); foreach my $key (keys %{$self->{maps_counters}}) { foreach (@{$self->{maps_counters}->{$key}}) { my $label = $_->{label}; my $thlabel = $label; if ($self->{output}->use_new_perfdata() && defined($_->{nlabel})) { $label = $_->{nlabel}; $thlabel = $self->get_threshold_prefix(name => $key) . $label; } $thlabel =~ s/\./-/g; if (!defined($_->{threshold}) || $_->{threshold} != 0) { $options{options}->add_options(arguments => { 'unknown-' . $thlabel . ':s' => { name => 'unknown-' . $thlabel, default => $_->{unknown_default} }, 'warning-' . $thlabel . ':s' => { name => 'warning-' . $thlabel, default => $_->{warning_default} }, 'critical-' . $thlabel . ':s' => { name => 'critical-' . $thlabel, default => $_->{critical_default} } }); if (defined($_->{nlabel})) { $options{options}->add_options(arguments => { 'unknown-' . $_->{label} . ':s' => { name => 'unknown-' . $_->{label}, redirect => 'unknown-' . $thlabel }, 'warning-' . $_->{label} . ':s' => { name => 'warning-' . $_->{label}, redirect => 'warning-' . $thlabel }, 'critical-' . $_->{label} . ':s' => { name => 'critical-' . $_->{label}, redirect => 'critical-' . $thlabel } }); } } $_->{obj} = centreon::plugins::values->new( statefile => $self->{statefile_value}, output => $self->{output}, perfdata => $self->{perfdata}, label => $_->{label}, nlabel => $_->{nlabel}, thlabel => $thlabel ); $_->{obj}->set(%{$_->{set}}); } } return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (defined($self->{option_results}->{list_counters})) { my $list_counter = ''; my $th_counter = ''; my $counters; foreach my $key (keys %{$self->{maps_counters}}) { foreach (@{$self->{maps_counters}->{$key}}) { $counters->{metrics}->{$_->{label}}->{nlabel} =""; $counters->{metrics}->{$_->{label}}->{min}=""; $counters->{metrics}->{$_->{label}}->{max}=""; $counters->{metrics}->{$_->{label}}->{unit}=""; $counters->{metrics}->{$_->{label}}->{output_template}=""; if(defined($_->{nlabel})) { $counters->{metrics}->{$_->{label}}->{nlabel} = $_->{nlabel}; } if(defined($_->{set}->{perfdatas}->[0]->{min})) { $counters->{metrics}->{$_->{label}}->{min} = $_->{set}->{perfdatas}->[0]->{min}; } if(defined($_->{set}->{perfdatas}->[0]->{max})) { $counters->{metrics}->{$_->{label}}->{max} = $_->{set}->{perfdatas}->[0]->{max}; } if(defined($_->{set}->{perfdatas}->[0]->{unit})) { $counters->{metrics}->{$_->{label}}->{unit} = $_->{set}->{perfdatas}->[0]->{unit}; } if(defined($_->{set}->{perfdatas}->[0]->{template})) { $counters->{metrics}->{$_->{label}}->{output_template} = $_->{set}->{perfdatas}->[0]->{template}; } my $label = $_->{label}; $label =~ s/-//g; $list_counter .= $_->{label}." "; $th_counter .= " --warning-$_->{label}='\$_SERVICEWARNING" . uc($label) . "\$' --critical-$_->{label}='\$_SERVICECRITICAL" . uc($label) . "\$'"; } } $counters->{"counter list"}=$list_counter; $counters->{"pack configuration"}=$th_counter." \$_SERVICEEXTRAOPTIONS\$"; my $result_data =""; eval { $result_data = JSON::XS->new->indent->space_after->canonical->utf8->encode($counters); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot use \$counters as it is a malformed JSON: " . $@); $self->{output}->option_exit(); } $self->{output}->output_add(short_msg => "counter list: ".$list_counter); $self->{output}->output_add(long_msg => $result_data); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1); $self->{output}->exit(); } my $change_macros_opt = []; foreach my $key (keys %{$self->{maps_counters}}) { foreach (@{$self->{maps_counters}->{$key}}) { push @$change_macros_opt, 'unknown-' . $_->{label}, 'warning-' . $_->{label}, 'critical-' . $_->{label} if (defined($_->{type}) && $_->{type} == 2); $_->{obj}->{instance_mode} = $self; $_->{obj}->init(option_results => $self->{option_results}) if (!defined($_->{type}) || $_->{type} != 2); } } $self->change_macros(macros => $change_macros_opt) if (scalar(@$change_macros_opt) > 0); if (defined($self->{statefile_value})) { $self->{statefile_value}->check_options(%options); } } sub run_global { my ($self, %options) = @_; return undef if (defined($self->{option_results}->{filter_counters_block}) && $self->{option_results}->{filter_counters_block} ne '' && $options{config}->{name} =~ /$self->{option_results}->{filter_counters_block}/); return undef if (defined($options{config}->{cb_init}) && $self->call_object_callback(method_name => $options{config}->{cb_init}) == 1); my $resume = defined($options{resume}) && $options{resume} == 1 ? 1 : 0; my $display_short = (!defined($options{config}->{display_short}) || $options{config}->{display_short} != 0) ? 1 : 0; # Can be set when it comes from type 3 counters my $called_multiple = defined($options{called_multiple}) && $options{called_multiple} == 1 ? 1 : 0; my $multiple_parent = defined($options{multiple_parent}) && $options{multiple_parent} == 1 ? 1 : 0; my $force_instance = defined($options{force_instance}) ? $options{force_instance} : undef; my $message_separator = defined($options{config}->{message_separator}) ? $options{config}->{message_separator}: ', '; my ($short_msg, $short_msg_append, $long_msg, $long_msg_append) = ('', '', '', ''); my @exits; foreach (@{$self->{maps_counters}->{$options{config}->{name}}}) { my $obj = $_->{obj}; next if (defined($self->{option_results}->{filter_counters}) && $self->{option_results}->{filter_counters} ne '' && $_->{label} !~ /$self->{option_results}->{filter_counters}/); $obj->set(instance => defined($force_instance) ? $force_instance : $options{config}->{name}); my ($value_check) = $obj->execute(new_datas => $self->{new_datas}, values => $self->{$options{config}->{name}}); next if (defined($options{config}->{skipped_code}) && defined($options{config}->{skipped_code}->{$value_check})); if ($value_check != 0) { $long_msg .= $long_msg_append . $obj->output_error(); $long_msg_append = $message_separator; next; } my $exit2 = $obj->threshold_check(); push @exits, $exit2; my $output = $obj->output(); if (!defined($_->{display_ok}) || $_->{display_ok} != 0 || (defined($self->{option_results}->{display_ok_counters}) && $self->{option_results}->{display_ok_counters} ne '' && $_->{label} =~ /$self->{option_results}->{display_ok_counters}/)) { $long_msg .= $long_msg_append . $output; $long_msg_append = $message_separator; } if (!$self->{output}->is_status(litteral => 1, value => $exit2, compare => 'ok')) { $short_msg .= $short_msg_append . $output; $short_msg_append = $message_separator; } $obj->perfdata(extra_instance => $multiple_parent); } my ($prefix_output, $suffix_output); $prefix_output = $self->call_object_callback(method_name => $options{config}->{cb_prefix_output}, instance_value => $self->{$options{config}->{name}}) if (defined($options{config}->{cb_prefix_output})); $prefix_output = '' if (!defined($prefix_output)); $suffix_output = $self->call_object_callback(method_name => $options{config}->{cb_suffix_output}, instance_value => $self->{$options{config}->{name}}) if (defined($options{config}->{cb_suffix_output})); $suffix_output = '' if (!defined($suffix_output)); if ($called_multiple == 1 && $long_msg ne '') { $self->{output}->output_add(long_msg => $options{indent_long_output} . $prefix_output. $long_msg . $suffix_output); } my $exit = $self->{output}->get_most_critical(status => [ @exits ]); if (!$self->{output}->is_status(litteral => 1, value => $exit, compare => 'ok')) { if ($called_multiple == 0) { $self->{output}->output_add( severity => $exit, short_msg => $prefix_output . $short_msg . $suffix_output ); } else { $self->run_multiple_prefix_output( severity => $exit, short_msg => $prefix_output . $short_msg . $suffix_output ); } } else { if ($long_msg ne '' && $multiple_parent == 0) { if ($called_multiple == 0) { $self->{output}->output_add(short_msg => $prefix_output . $long_msg . $suffix_output) if ($display_short == 1); } else { $self->run_multiple_prefix_output( severity => 'ok', short_msg => $prefix_output . $long_msg . $suffix_output ) if ($display_short == 1); } } } } sub run_instances { my ($self, %options) = @_; return undef if (defined($self->{option_results}->{filter_counters_block}) && $self->{option_results}->{filter_counters_block} ne '' && $options{config}->{name} =~ /$self->{option_results}->{filter_counters_block}/); return undef if (defined($options{config}->{cb_init}) && $self->call_object_callback(method_name => $options{config}->{cb_init}) == 1); my $cb_init_counters = $self->get_callback(method_name => $options{config}->{cb_init_counters}); my $display_status_lo = defined($options{display_status_long_output}) && $options{display_status_long_output} == 1 ? 1 : 0; my $display_short = (!defined($options{config}->{display_short}) || $options{config}->{display_short} != 0) ? 1 : 0; my $display_long = (!defined($options{config}->{display_long}) || $options{config}->{display_long} != 0) ? 1 : 0; my $resume = defined($options{resume}) && $options{resume} == 1 ? 1 : 0; my $no_message_multiple = 1; $self->{lproblems} = 0; $self->{multiple} = 1; if (scalar(keys %{$self->{$options{config}->{name}}}) <= 1) { $self->{multiple} = 0; } my $message_separator = defined($options{config}->{message_separator}) ? $options{config}->{message_separator}: ', '; my $sort_method = 'cmp'; $sort_method = $options{config}->{sort_method} if (defined($options{config}->{sort_method})); foreach my $id (sort { $sort_subs->{$sort_method}->() } keys %{$self->{$options{config}->{name}}}) { my ($short_msg, $short_msg_append, $long_msg, $long_msg_append) = ('', '', '', ''); my @exits = (); foreach (@{$self->{maps_counters}->{$options{config}->{name}}}) { my $obj = $_->{obj}; next if (defined($self->{option_results}->{filter_counters}) && $self->{option_results}->{filter_counters} ne '' && $_->{label} !~ /$self->{option_results}->{filter_counters}/); next if ($cb_init_counters && $self->$cb_init_counters(%$_) == 1); $no_message_multiple = 0; $obj->set(instance => $id); my ($value_check) = $obj->execute( new_datas => $self->{new_datas}, values => $self->{$options{config}->{name}}->{$id} ); next if (defined($options{config}->{skipped_code}) && defined($options{config}->{skipped_code}->{$value_check})); if ($value_check != 0) { $long_msg .= $long_msg_append . $obj->output_error(); $long_msg_append = $message_separator; next; } my $exit2 = $obj->threshold_check(); push @exits, $exit2; my $output = $obj->output(); if (!defined($_->{display_ok}) || $_->{display_ok} != 0 || (defined($self->{option_results}->{display_ok_counters}) && $self->{option_results}->{display_ok_counters} ne '' && $_->{label} =~ /$self->{option_results}->{display_ok_counters}/)) { $long_msg .= $long_msg_append . $output; $long_msg_append = $message_separator; } if (!$self->{output}->is_status(litteral => 1, value => $exit2, compare => 'ok')) { $self->{lproblems}++; $short_msg .= $short_msg_append . $output; $short_msg_append = $message_separator; } $obj->perfdata(extra_instance => $self->{multiple}); } my ($prefix_output, $suffix_output); $prefix_output = $self->call_object_callback(method_name => $options{config}->{cb_prefix_output}, instance => $id, instance_value => $self->{$options{config}->{name}}->{$id}) if (defined($options{config}->{cb_prefix_output})); $prefix_output = '' if (!defined($prefix_output)); $suffix_output = $self->call_object_callback(method_name => $options{config}->{cb_suffix_output}) if (defined($options{config}->{cb_suffix_output})); $suffix_output = '' if (!defined($suffix_output)); my $exit = $self->{output}->get_most_critical(status => [ @exits ]); # in mode grouped, we don't display 'ok' my $debug = 0; $debug = 1 if ($display_status_lo == 1 && $self->{output}->is_status(value => $exit, compare => 'OK', litteral => 1)); if (scalar @{$self->{maps_counters}->{$options{config}->{name}}} > 0 && $long_msg ne '') { $self->{output}->output_add(long_msg => ($display_status_lo == 1 ? lc($exit) . ': ' : '') . $prefix_output . $long_msg . $suffix_output, debug => $debug) if ($display_long == 1); } if ($resume == 1) { $self->{most_critical_instance} = $self->{output}->get_most_critical(status => [ $self->{most_critical_instance}, $exit ]); next; } if (!$self->{output}->is_status(litteral => 1, value => $exit, compare => 'ok')) { $self->{output}->output_add( severity => $exit, short_msg => $prefix_output . $short_msg . $suffix_output ); } if ($self->{multiple} == 0) { $self->{output}->output_add(short_msg => $prefix_output . $long_msg . $suffix_output) if ($display_short == 1); } } if ($no_message_multiple == 0 && $self->{multiple} == 1 && $resume == 0) { $self->{output}->output_add(short_msg => $options{config}->{message_multiple}) if ($display_short == 1); } } sub run_group { my ($self, %options) = @_; my $multiple = 1; return if (scalar(keys %{$self->{$options{config}->{name}}}) <= 0); if (scalar(keys %{$self->{$options{config}->{name}}}) <= 1) { $multiple = 0; } if ($multiple == 1) { $self->{output}->output_add( severity => 'OK', short_msg => $options{config}->{message_multiple} ); } my $format_output = defined($options{config}->{format_output}) ? $options{config}->{format_output} : '%s problem(s) detected'; my ($global_exit, $total_problems) = ([], 0); foreach my $id (sort keys %{$self->{$options{config}->{name}}}) { $self->{most_critical_instance} = 'ok'; if (defined($options{config}->{cb_long_output})) { $self->{output}->output_add( long_msg => $self->call_object_callback( method_name => $options{config}->{cb_long_output}, instance => $id, instance_value => $self->{$options{config}->{name}}->{$id} ) ); } foreach my $group (@{$options{config}->{group}}) { $self->{$group->{name}} = $self->{$options{config}->{name}}->{$id}->{$group->{name}}; # we resume datas $self->run_instances(config => $group, display_status_long_output => 1, resume => 1); push @{$global_exit}, $self->{most_critical_instance}; $total_problems += $self->{lproblems}; my $prefix_output; $prefix_output = $self->call_object_callback(method_name => $options{config}->{cb_prefix_output}, instance => $id, instance_value => $self->{$options{config}->{name}}->{$id}) if (defined($options{config}->{cb_prefix_output})); $prefix_output = '' if (!defined($prefix_output)); if ($multiple == 0 && (!defined($group->{display}) || $group->{display} != 0)) { $self->{output}->output_add( severity => $self->{most_critical_instance}, short_msg => sprintf("${prefix_output}" . $format_output, $self->{lproblems}) ); } } } if ($multiple == 1) { my $exit = $self->{output}->get_most_critical(status => [ @{$global_exit} ]); if (!$self->{output}->is_status(litteral => 1, value => $exit, compare => 'ok')) { $self->{output}->output_add( severity => $exit, short_msg => sprintf($format_output, $total_problems) ); } } if (defined($options{config}->{display_counter_problem})) { $self->{output}->perfdata_add( label => $options{config}->{display_counter_problem}->{label}, nlabel => $options{config}->{display_counter_problem}->{nlabel}, unit => $options{config}->{display_counter_problem}->{unit}, value => $total_problems, min => $options{config}->{display_counter_problem}->{min}, max => $options{config}->{display_counter_problem}->{max} ); } } sub run_multiple_instances { my ($self, %options) = @_; return undef if (defined($self->{option_results}->{filter_counters_block}) && $self->{option_results}->{filter_counters_block} ne '' && $options{config}->{name} =~ /$self->{option_results}->{filter_counters_block}/); return undef if (defined($options{config}->{cb_init}) && $self->call_object_callback(method_name => $options{config}->{cb_init}) == 1); my $use_new_perfdata = $self->{output}->use_new_perfdata(); my $multiple_parent = defined($options{multiple_parent}) && $options{multiple_parent} == 1 ? $options{multiple_parent} : 0; my $indent_long_output = defined($options{indent_long_output}) ? $options{indent_long_output} : ''; my $no_message_multiple = 1; my $display_long = (!defined($options{config}->{display_long}) || $options{config}->{display_long} != 0) ? 1 : 0; my $display_short = (!defined($options{config}->{display_short}) || $options{config}->{display_short} != 0) ? 1 : 0; my $multiple = 1; if (scalar(keys %{$self->{$options{config}->{name}}}) <= 1) { $multiple = 0; } my $message_separator = defined($options{config}->{message_separator}) ? $options{config}->{message_separator} : ', '; my $sort_method = 'cmp'; $sort_method = $options{config}->{sort_method} if (defined($options{config}->{sort_method})); foreach my $id (sort { $sort_subs->{$sort_method}->() } keys %{$self->{$options{config}->{name}}}) { my ($short_msg, $short_msg_append, $long_msg, $long_msg_append) = ('', '', '', ''); my @exits = (); foreach (@{$self->{maps_counters}->{$options{config}->{name}}}) { my $obj = $_->{obj}; next if (defined($self->{option_results}->{filter_counters}) && $self->{option_results}->{filter_counters} ne '' && $_->{label} !~ /$self->{option_results}->{filter_counters}/); my $instance = $id; if ($use_new_perfdata || ($multiple_parent == 1 && $multiple == 1)) { $instance = $options{instance_parent} . ($self->{output}->get_instance_perfdata_separator()) . $id; } elsif ($multiple_parent == 1 && $multiple == 0) { $instance = $options{instance_parent}; } $no_message_multiple = 0; $obj->set(instance => $instance); my ($value_check) = $obj->execute( new_datas => $self->{new_datas}, values => $self->{$options{config}->{name}}->{$id} ); next if (defined($options{config}->{skipped_code}) && defined($options{config}->{skipped_code}->{$value_check})); if ($value_check != 0) { $long_msg .= $long_msg_append . $obj->output_error(); $long_msg_append = $message_separator; next; } my $exit2 = $obj->threshold_check(); push @exits, $exit2; my $output = $obj->output(); if (!defined($_->{display_ok}) || $_->{display_ok} != 0 || (defined($self->{option_results}->{display_ok_counters}) && $self->{option_results}->{display_ok_counters} ne '' && $_->{label} =~ /$self->{option_results}->{display_ok_counters}/)) { $long_msg .= $long_msg_append . $output; $long_msg_append = $message_separator; } if (!$self->{output}->is_status(litteral => 1, value => $exit2, compare => 'ok')) { $short_msg .= $short_msg_append . $output; $short_msg_append = $message_separator; } if ($multiple_parent == 1 && $multiple == 0) { $obj->perfdata(extra_instance => 1); } else { $obj->perfdata(extra_instance => $multiple); } } my ($prefix_output, $suffix_output); $prefix_output = $self->call_object_callback(method_name => $options{config}->{cb_prefix_output}, instance => $id, instance_value => $self->{$options{config}->{name}}->{$id}) if (defined($options{config}->{cb_prefix_output})); $prefix_output = '' if (!defined($prefix_output)); $suffix_output = $self->call_object_callback(method_name => $options{config}->{cb_suffix_output}) if (defined($options{config}->{cb_suffix_output})); $suffix_output = '' if (!defined($suffix_output)); my $exit = $self->{output}->get_most_critical(status => [ @exits ]); if (scalar @{$self->{maps_counters}->{$options{config}->{name}}} > 0 && $long_msg ne '') { $self->{output}->output_add(long_msg => $indent_long_output . $prefix_output . $long_msg . $suffix_output) if ($display_long == 1); } if (!$self->{output}->is_status(litteral => 1, value => $exit, compare => 'ok')) { $self->run_multiple_prefix_output( severity => $exit, short_msg => $prefix_output . $short_msg . $suffix_output ); } if ($multiple == 0 && $multiple_parent == 0) { $self->run_multiple_prefix_output(severity => 'ok', short_msg => $prefix_output . $long_msg . $suffix_output) if ($display_short == 1); } } if ($no_message_multiple == 0 && $multiple == 1 && $multiple_parent == 0) { $self->run_multiple_prefix_output(severity => 'ok', short_msg => $options{config}->{message_multiple}) if ($display_short == 1); } } sub run_multiple_prefix_output { my ($self, %options) = @_; my %separator; if ($self->{prefix_multiple_output_done}->{lc($options{severity})} == 0) { $self->{output}->output_add(severity => $options{severity}, short_msg => $self->{prefix_multiple_output}); $self->{prefix_multiple_output_done}->{lc($options{severity})} = 1; $separator{separator} = ''; } $self->{output}->output_add(severity => $options{severity}, short_msg => $options{short_msg}, %separator); } sub run_multiple { my ($self, %options) = @_; my $multiple = 1; if (scalar(keys %{$self->{$options{config}->{name}}}) <= 1) { $multiple = 0; } if ($multiple == 1) { $self->{output}->output_add( severity => 'OK', short_msg => $options{config}->{message_multiple} ); } foreach my $instance (sort keys %{$self->{$options{config}->{name}}}) { if (defined($options{config}->{cb_long_output})) { $self->{output}->output_add( long_msg => $self->call_object_callback( method_name => $options{config}->{cb_long_output}, instance => $instance, instance_value => $self->{$options{config}->{name}}->{$instance} ) ); } $self->{prefix_multiple_output} = ''; $self->{prefix_multiple_output_done} = { ok => 0, warning => 0, critical => 0, unknown => 0 }; $self->{prefix_multiple_output} = $self->call_object_callback(method_name => $options{config}->{cb_prefix_output}, instance => $instance, instance_value => $self->{$options{config}->{name}}->{$instance}) if (defined($options{config}->{cb_prefix_output})); my $indent_long_output = ''; $indent_long_output = $options{config}->{indent_long_output} if (defined($options{config}->{indent_long_output})); foreach my $group (@{$options{config}->{group}}) { next if (!defined($self->{$options{config}->{name}}->{$instance}->{$group->{name}})); $self->{$group->{name}} = $self->{$options{config}->{name}}->{$instance}->{$group->{name}}; if ($group->{type} == 1) { $self->run_multiple_instances(config => $group, multiple_parent => $multiple, instance_parent => $instance, indent_long_output => $indent_long_output); } elsif ($group->{type} == 0) { $self->run_global( config => $group, multiple_parent => $multiple, called_multiple => 1, force_instance => $instance, indent_long_output => $indent_long_output ); } } } } sub read_statefile_key { my ($self, %options) = @_; $self->{statefile_value}->read(statefile => $self->{cache_name}); return $self->{statefile_value}->get(name => $options{key}); } sub set_timestamp { my ($self, %options) = @_; $self->{override_timestamp} = $options{timestamp}; } sub run { my ($self, %options) = @_; $self->manage_selection(%options); $self->{new_datas} = undef; if (defined($self->{statefile_value})) { $self->{new_datas} = {}; $self->{statefile_value}->read(statefile => $self->{cache_name}) if (defined($self->{cache_name})); $self->{new_datas}->{last_timestamp} = defined($self->{override_timestamp}) ? $self->{override_timestamp} : time(); } foreach my $entry (@{$self->{maps_counters_type}}) { if ($entry->{type} == 0) { $self->run_global(config => $entry); } elsif ($entry->{type} == 1) { $self->run_instances(config => $entry); } elsif ($entry->{type} == 2) { $self->run_group(config => $entry); } elsif ($entry->{type} == 3) { $self->run_multiple(config => $entry); } } if (defined($self->{statefile_value})) { $self->{statefile_value}->write(data => $self->{new_datas}); } $self->{output}->display(); $self->{output}->exit(); } sub manage_selection { my ($self, %options) = @_; # example for snmp #use Digest::MD5 qw(md5_hex); #$self->{cache_name} = "choose_name_" . $options{snmp}->get_hostname() . '_' . $options{snmp}->get_port() . '_' . $self->{mode} . '_' . # (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')); } sub compat_threshold_counter { my ($self, %options) = @_; foreach ('warning', 'critical') { foreach my $th (@{$options{compat}->{th}}) { next if (!defined($options{option_results}->{$_ . '-' . $th->[0]}) || $options{option_results}->{$_ . '-' . $th->[0]} eq ''); my $src_threshold = $options{option_results}->{$_ . '-' . $th->[0]}; if (defined($options{compat}->{units}) && $options{compat}->{units} eq '%') { $options{option_results}->{$_ . '-' . $th->[0]} = undef; if (defined($options{compat}->{free})) { $options{option_results}->{$_ . '-' . $th->[0]} = undef; my ($status, $result) = centreon::plugins::misc::parse_threshold(threshold => $src_threshold); next if ($status == 0); my $tmp = { arobase => $result->{arobase}, infinite_pos => 0, infinite_neg => 0, start => $result->{start}, end => $result->{end} }; $tmp->{infinite_neg} = 1 if ($result->{infinite_pos} == 1); $tmp->{infinite_pos} = 1 if ($result->{infinite_neg} == 1); if ($result->{start} ne '' && $result->{infinite_neg} == 0) { $tmp->{end} = 100 - $result->{start}; } if ($result->{end} ne '' && $result->{infinite_pos} == 0) { $tmp->{start} = 100 - $result->{end}; } $options{option_results}->{$_ . '-' . $th->[1]->{prct}} = centreon::plugins::misc::get_threshold_litteral(%$tmp); } else { $options{option_results}->{$_ . '-' . $th->[1]->{prct}} = $src_threshold; } } elsif (defined($options{compat}->{free})) { $options{option_results}->{$_ . '-' . $th->[1]->{free}} = $options{option_results}->{$_ . '-' . $th->[0]}; $options{option_results}->{$_ . '-' . $th->[0]} = undef; } } } } sub change_macros { my ($self, %options) = @_; foreach (@{$options{macros}}) { if (defined($self->{option_results}->{$_}) && $self->{option_results}->{$_} ne '') { $self->{option_results}->{$_} =~ s/%\{(.*?)\}/\$values->{$1}/g; } } } sub custom_perfdata_instances { my ($self, %options) = @_; my $instances = []; foreach (split(/\s+/, $options{instances})) { while (/%\((.+?)\)/g) { my $name = $1; if (!defined($options{labels}->{$name})) { $self->{output}->add_option_msg(short_msg => "option $options{option_name} unsupported label: %($name)"); $self->{output}->option_exit(); } push @$instances, $name; } } if (scalar(@$instances) <= 0) { $self->{output}->add_option_msg(short_msg => "option $options{option_name} need at least one label"); $self->{output}->option_exit(); } return $instances; } 1; =head1 MODE Default template for counters. Should be extended. =over 8 =item B<--filter-counters> Only display some counters (regexp can be used). Example to check SSL connections only : --filter-counters='^xxxx|yyyy$' =item B<--warning-*> Warning threshold. Can be: 'xxx', 'xxx'. =item B<--critical-*> Critical threshold. Can be: 'xxx', 'xxx'. =back =cut CENTREON_PLUGINS_TEMPLATES_COUNTER $fatpacked{"centreon/plugins/templates/hardware.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_TEMPLATES_HARDWARE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::templates::hardware; use base qw(centreon::plugins::mode); use strict; use warnings; sub set_system { my ($self, %options) = @_; #$self->{regexp_threshold_numeric_check_section_option} = ''; #$self->{cb_threshold_numeric_check_section_option} = 'callbackname'; # Some callbacks #$self->{cb_hook1} = 'callbackname'; # before the loads #$self->{cb_hook2} = 'callbackname'; # between loads and requests #$self->{cb_hook3} = 'callbackname'; # after requests #$self->{cb_hook4} = 'callbackname'; # after output # Example for threshold: #$self->{thresholds} = { # fan => [ # ['bad', 'CRITICAL'], # ['good', 'OK'], # ['notPresent', 'OK'], # ], #}; # Unset the call to load components #$self->{components_exec_load} = 0; # Set the path_info #$self->{components_path} = 'network::xxxx::mode::components'; # Set the components #$self->{components_module} = ['cpu', 'memory', ...]; } sub call_object_callback { my ($self, %options) = @_; if (defined($options{method_name})) { my $method = $self->can($options{method_name}); if ($method) { return $self->$method(%options); } } return undef; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $self->{version} = '1.0'; $options{options}->add_options(arguments => { 'component:s' => { name => 'component', default => '.*' }, 'no-component:s' => { name => 'no_component' }, 'threshold-overload:s@' => { name => 'threshold_overload' }, 'add-name-instance' => { name => 'add_name_instance' }, 'no-component-count' => { name => 'no_component_count' } }); $self->{performance} = (defined($options{no_performance}) && $options{no_performance} == 1) ? 0 : 1; if ($self->{performance} == 1) { $options{options}->add_options(arguments => { 'warning:s@' => { name => 'warning' }, 'critical:s@' => { name => 'critical' } }); } $self->{filter_exclude} = (defined($options{no_filter_exclude}) && $options{no_filter_exclude} == 1) ? 0 : 1; if ($self->{filter_exclude} == 1) { $options{options}->add_options(arguments => { 'exclude:s' => { name => 'exclude' }, 'filter:s@' => { name => 'filter' } }); } $self->{absent} = (defined($options{no_absent}) && $options{no_absent} == 1) ? 0 : 1; if ($self->{absent} == 1) { $options{options}->add_options(arguments => { 'absent-problem:s@' => { name => 'absent_problem' } }); } $self->{load_components} = (defined($options{no_load_components}) && $options{no_load_components} == 1) ? 0 : 1; $self->{components} = {}; $self->{no_components} = undef; $self->{components_module} = []; $self->{components_exec_load} = 1; $self->set_system(); $self->{count} = (defined($options{no_count}) && $options{no_count} == 1) ? 0 : 1; if ($self->{count} == 1) { foreach my $component (@{$self->{components_module}}) { $options{options}->add_options(arguments => { 'unknown-count-' . $component . ':s' => { name => 'unknown_count_' . $component }, 'warning-count-' . $component . ':s' => { name => 'warning_count_' . $component }, 'critical-count-' . $component . ':s' => { name => 'critical_count_' . $component } }); } } $self->{request} = []; return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (defined($self->{option_results}->{no_component})) { if ($self->{option_results}->{no_component} ne '') { $self->{no_components} = $self->{option_results}->{no_component}; } else { $self->{no_components} = 'critical'; } } if ($self->{filter_exclude} == 1) { $self->{filter} = []; foreach my $val (@{$self->{option_results}->{filter}}) { next if (!defined($val) || $val eq ''); my @values = split (/,/, $val); push @{$self->{filter}}, { filter => $values[0], instance => $values[1] }; } } if ($self->{absent} == 1) { $self->{absent_problem} = []; foreach my $val (@{$self->{option_results}->{absent_problem}}) { next if (!defined($val) || $val eq ''); my @values = split (/,/, $val); push @{$self->{absent_problem}}, { filter => $values[0], instance => $values[1] }; } } $self->{overload_th} = []; foreach my $val (@{$self->{option_results}->{threshold_overload}}) { next if (!defined($val) || $val eq ''); my @values = split (/,/, $val); if (scalar(@values) < 3) { $self->{output}->add_option_msg(short_msg => "Wrong threshold-overload option '" . $val . "'."); $self->{output}->option_exit(); } my ($section, $instance, $status, $filter); if (scalar(@values) == 3) { ($section, $status, $filter) = @values; $instance = '.*'; } else { ($section, $instance, $status, $filter) = @values; } if ($self->{output}->is_litteral_status(status => $status) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong threshold-overload status '" . $val . "'."); $self->{output}->option_exit(); } push @{$self->{overload_th}}, { section => $section, filter => $filter, status => $status, instance => $instance }; } if ($self->{performance} == 1) { $self->{numeric_threshold} = {}; foreach my $option (('warning', 'critical')) { foreach my $val (@{$self->{option_results}->{$option}}) { next if (!defined($val) || $val eq ''); if ($val !~ /^(.*?),(.*?),(.*)$/) { $self->{output}->add_option_msg(short_msg => "Wrong $option option '" . $val . "'."); $self->{output}->option_exit(); } my ($section, $instance, $value) = ($1, $2, $3); if (defined($self->{regexp_threshold_numeric_check_section_option}) && $section !~ /$self->{regexp_threshold_numeric_check_section_option}/) { $self->{output}->add_option_msg(short_msg => "Wrong $option option '" . $val . "'."); $self->{output}->option_exit(); } $self->call_object_callback( method_name => $self->{cb_threshold_numeric_check_section_option}, section => $section, option_name => $option, option_value => $val ); my $position = 0; if (defined($self->{numeric_threshold}->{$section})) { $position = scalar(@{$self->{numeric_threshold}->{$section}}); } if (($self->{perfdata}->threshold_validate(label => $option . '-' . $section . '-' . $position, value => $value)) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong $option threshold '" . $value . "'."); $self->{output}->option_exit(); } $self->{numeric_threshold}->{$section} = [] if (!defined($self->{numeric_threshold}->{$section})); push @{$self->{numeric_threshold}->{$section}}, { label => $option . '-' . $section . '-' . $position, threshold => $option, instance => $instance }; } } } if ($self->{count} == 1) { foreach my $comp (@{$self->{components_module}}) { foreach my $threshold (('warning', 'critical', 'unknown')) { if (($self->{perfdata}->threshold_validate(label => $threshold . '-count-' . $comp, value => $self->{option_results}->{$threshold . '_count_' . $comp})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong " . $threshold . " threshold '" . $self->{option_results}->{$threshold . '_count_' . $comp} . "'."); $self->{output}->option_exit(); } } } } } sub load_components { my ($self, %options) = @_; foreach (@{$self->{components_module}}) { if (/$self->{option_results}->{component}/) { my $mod_name = $self->{components_path} . "::$_"; centreon::plugins::misc::mymodule_load( output => $self->{output}, module => $mod_name, error_msg => "Cannot load module '$mod_name'.") if ($self->{load_components} == 1); $self->{loaded} = 1; if ($self->{components_exec_load} == 1) { my $func = $mod_name->can('load'); $func->($self); } } } } sub exec_components { my ($self, %options) = @_; foreach (@{$self->{components_module}}) { if (/$self->{option_results}->{component}/) { my $mod_name = $self->{components_path} . "::$_"; my $func = $mod_name->can('check'); $func->($self); } } } sub display { my ($self, %options) = @_; my $total_components = 0; my $display_by_component = ''; my $display_by_component_append = ''; my $exit = 'OK'; my $exits = []; my ($warn, $crit); foreach my $comp (sort(keys %{$self->{components}})) { # Skipping short msg when no components next if (!defined($self->{option_results}->{no_component_count}) && $self->{components}->{$comp}->{total} == 0 && $self->{components}->{$comp}->{skip} == 0); next if (defined($self->{option_results}->{component}) && $comp !~ /$self->{option_results}->{component}/ ); if ($self->{count} == 1) { ($exit, $warn, $crit) = $self->get_severity_count(label => $comp, value => $self->{components}->{$comp}->{total}); if (!$self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1)) { $self->{output}->output_add( severity => $exit, short_msg => sprintf( "'%s' components '%s' checked", $self->{components}->{$comp}->{total}, $comp ) ); } $self->{output}->perfdata_add( label => 'count_' . $comp, nlabel => 'hardware.' . $comp . '.count', value => $self->{components}->{$comp}->{total}, warning => $warn, critical => $crit ); push @{$exits}, $exit; } $total_components += $self->{components}->{$comp}->{total} + $self->{components}->{$comp}->{skip}; my $count_by_components = $self->{components}->{$comp}->{total} + $self->{components}->{$comp}->{skip}; $display_by_component .= $display_by_component_append . $self->{components}->{$comp}->{total} . '/' . $count_by_components . ' ' . $self->{components}->{$comp}->{name}; $display_by_component_append = ', '; } $exit = $self->{output}->get_most_critical(status => $exits) if (scalar(@{$exits}) > 0); if ($self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1)) { $self->{output}->output_add( short_msg => sprintf( 'All %s components are ok [%s].', $total_components, $display_by_component ) ); } if (defined($self->{option_results}->{no_component}) && $total_components == 0) { $self->{output}->output_add( severity => $self->{no_components}, short_msg => 'No components are checked.' ); } } sub run { my ($self, %options) = @_; $self->{loaded} = 0; $self->call_object_callback(method_name => $self->{cb_hook1}, %options); $self->load_components(%options); if ($self->{loaded} == 0) { $self->{output}->add_option_msg(short_msg => "Wrong option. Cannot find component '" . $self->{option_results}->{component} . "'."); $self->{output}->option_exit(); } $self->call_object_callback(method_name => $self->{cb_hook2}, %options); $self->exec_components(%options); $self->call_object_callback(method_name => $self->{cb_hook3}, %options); $self->display(); $self->call_object_callback(method_name => $self->{cb_hook4}, %options); $self->{output}->display(); $self->{output}->exit(); } sub check_filter { my ($self, %options) = @_; # Old compatibility variable. We'll be deleted if (defined($self->{option_results}->{exclude})) { if (defined($options{instance})) { if ($self->{option_results}->{exclude} =~ /(^|\s|,)${options{section}}[^,]*#\Q$options{instance}\E#/) { $self->{components}->{$options{section}}->{skip}++; $self->{output}->output_add(long_msg => sprintf("skipping $options{section} section $options{instance} instance.")); return 1; } } elsif (defined($self->{option_results}->{exclude}) && $self->{option_results}->{exclude} =~ /(^|\s|,)$options{section}(\s|,|$)/) { $self->{output}->output_add(long_msg => sprintf("skipping $options{section} section.")); return 1; } } $options{instance} .= '#' . $options{name} if (defined($self->{option_results}->{add_name_instance}) && defined($options{name})); foreach (@{$self->{filter}}) { if ($options{section} =~ /$_->{filter}/) { if (!defined($options{instance}) && !defined($_->{instance})) { $self->{output}->output_add(long_msg => sprintf("skipping $options{section} section.")); return 1; } elsif (defined($options{instance}) && $options{instance} =~ /$_->{instance}/) { $self->{output}->output_add(long_msg => sprintf("skipping $options{section} section $options{instance} instance.")); return 1; } } } return 0; } sub absent_problem { my ($self, %options) = @_; $options{instance} .= '#' . $options{name} if (defined($self->{option_results}->{add_name_instance}) && defined($options{name})); foreach (@{$self->{absent_problem}}) { if ($options{section} =~ /$_->{filter}/) { if (!defined($_->{instance}) || $options{instance} =~ /$_->{instance}/) { $self->{output}->output_add( severity => 'CRITICAL', short_msg => sprintf( "Component '%s' instance '%s' is not present", $options{section}, $options{instance} ) ); $self->{output}->output_add(long_msg => sprintf("Skipping $options{section} section $options{instance} instance (not present)")); $self->{components}->{$options{section}}->{skip}++; return 1; } } } return 0; } sub get_severity_count { my ($self, %options) = @_; my $status = 'OK'; # default my $thresholds = { warning => undef, critical => undef }; $status = $self->{perfdata}->threshold_check( value => $options{value}, threshold => [ { label => 'critical-count-' . $options{label}, exit_litteral => 'critical' }, { label => 'warning-count-' . $options{label}, exit_litteral => 'warning' }, { label => 'unknown-count-' . $options{label}, exit_litteral => 'unknown' }, ] ); $thresholds->{critical} = $self->{perfdata}->get_perfdata_for_output(label => 'critical-count-' . $options{label}); $thresholds->{warning} = $self->{perfdata}->get_perfdata_for_output(label => 'warning-count-' . $options{label}); return ($status, $thresholds->{warning}, $thresholds->{critical}); } sub get_severity_numeric { my ($self, %options) = @_; my $status = 'OK'; # default my $thresholds = { warning => undef, critical => undef }; my $checked = 0; $options{instance} .= '#' . $options{name} if (defined($self->{option_results}->{add_name_instance}) && defined($options{name})); if (defined($self->{numeric_threshold}->{$options{section}})) { my $exits = []; foreach (@{$self->{numeric_threshold}->{$options{section}}}) { if ($options{instance} =~ /$_->{instance}/) { push @{$exits}, $self->{perfdata}->threshold_check(value => $options{value}, threshold => [ { label => $_->{label}, exit_litteral => $_->{threshold} } ]); $thresholds->{$_->{threshold}} = $self->{perfdata}->get_perfdata_for_output(label => $_->{label}); $checked = 1; } } $status = $self->{output}->get_most_critical(status => $exits) if (scalar(@{$exits}) > 0); } return ($status, $thresholds->{warning}, $thresholds->{critical}, $checked); } sub get_severity { my ($self, %options) = @_; my $status = 'UNKNOWN'; # default $options{instance} .= '#' . $options{name} if (defined($self->{option_results}->{add_name_instance}) && defined($options{name})); foreach (@{$self->{overload_th}}) { if ($options{section} =~ /$_->{section}/i) { if ($options{value} =~ /$_->{filter}/i && (!defined($options{instance}) || $options{instance} =~ /$_->{instance}/)) { $status = $_->{status}; return $status; } } } my $label = defined($options{label}) ? $options{label} : $options{section}; foreach (@{$self->{thresholds}->{$label}}) { if ($options{value} =~ /$$_[0]/i) { $status = $$_[1]; return $status; } } return $status; } 1; =head1 MODE Default template for hardware. Should be extended. =over 8 =item B<--component> Define which component should be monitored based on their names. This option will be treated as a regular expression. All components are included by default ('.*'). =item B<--filter> Exclude some components. This option can be called several times (example: --filter=component1 --filter=component2). You can also exclude components from a specific instance (example: --filter=component_name,instance_value). =item B<--absent-problem> Return an error if a component is not 'present' (default is skipping). It can be set globally or for a specific instance: --absent-problem='component_name' or --absent-problem='component_name,instance_value'. =item B<--no-component> Define the expected status if no components are found (default: critical). =item B<--threshold-overload> Use this option to override the status returned by the plugin when the status label matches a regular expression (syntax: section,[instance,]status,regexp). Example: --threshold-overload='xxxxx,CRITICAL,^(?!(normal)$)' =item B<--warning> Define the warning threshold for temperatures (syntax: type,instance,threshold) Example: --warning='temperature,.*,30' =item B<--critical> Define the critical threshold for temperatures (syntax: type,instance,threshold) Example: --critical='temperature,.*,40' =item B<--warning-count-*> Define the warning threshold for the number of components of one type (replace '*' with the component type). =item B<--critical-count-*> Define the critical threshold for the number of components of one type (replace '*' with the component type). =back =cut CENTREON_PLUGINS_TEMPLATES_HARDWARE $fatpacked{"centreon/plugins/values.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CENTREON_PLUGINS_VALUES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package centreon::plugins::values; use strict; use warnings; use centreon::plugins::misc; # Warning message with sprintf and too much arguments. # Really annoying. Need to disable that warning no if ($^V gt v5.22.0), 'warnings' => 'redundant'; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; $self->{statefile} = $options{statefile}; $self->{output} = $options{output}; $self->{perfdata} = $options{perfdata}; $self->{label} = $options{label}; $self->{nlabel} = $options{nlabel}; $self->{thlabel} = defined($options{thlabel}) ? $options{thlabel} : $self->{label}; $self->{perfdatas} = []; $self->{output_template} = $self->{label} . ' : %s'; $self->{output_use} = undef; $self->{output_change_bytes} = 0; $self->{output_error_template} = $self->{label} . ' : %s'; $self->{threshold_use} = undef; $self->{threshold_warn} = undef; $self->{threshold_crit} = undef; $self->{per_second} = 0; $self->{manual_keys} = 0; $self->{last_timestamp} = undef; $self->{result_values} = {}; $self->{safe_test} = 0; return $self; } sub init { my ($self, %options) = @_; my $unkn = defined($self->{threshold_unkn}) ? $self->{threshold_unkn} : 'unknown-' . $self->{thlabel}; my $warn = defined($self->{threshold_warn}) ? $self->{threshold_warn} : 'warning-' . $self->{thlabel}; my $crit = defined($self->{threshold_crit}) ? $self->{threshold_crit} : 'critical-' . $self->{thlabel}; if (($self->{perfdata}->threshold_validate(label => $unkn, value => $options{option_results}->{$unkn})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong $unkn threshold '" . $options{option_results}->{$unkn} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => $warn, value => $options{option_results}->{$warn})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong $warn threshold '" . $options{option_results}->{$warn} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => $crit, value => $options{option_results}->{$crit})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong $crit threshold '" . $options{option_results}->{$crit} . "'."); $self->{output}->option_exit(); } } sub set { my ($self, %options) = @_; foreach (keys %options) { $self->{$_} = $options{$_}; } } sub calc { my ($self, %options) = @_; # manage only one value ;) foreach my $value (@{$self->{key_values}}) { if (defined($value->{diff}) && $value->{diff} == 1) { $self->{result_values}->{$value->{name}} = $options{new_datas}->{$self->{instance} . '_' . $value->{name}} - $options{old_datas}->{$self->{instance} . '_' . $value->{name}}; } elsif (defined($value->{per_second}) && $value->{per_second} == 1) { $self->{result_values}->{$value->{name}} = ($options{new_datas}->{$self->{instance} . '_' . $value->{name}} - $options{old_datas}->{$self->{instance} . '_' . $value->{name}}) / $options{delta_time}; } elsif (defined($value->{per_minute}) && $value->{per_minute} == 1) { $self->{result_values}->{$value->{name}} = ($options{new_datas}->{$self->{instance} . '_' . $value->{name}} - $options{old_datas}->{$self->{instance} . '_' . $value->{name}}) / ($options{delta_time} / 60); } else { $self->{result_values}->{$value->{name}} = $options{new_datas}->{$self->{instance} . '_' . $value->{name}}; } } return 0; } sub threshold_check { my ($self, %options) = @_; if (defined($self->{closure_custom_threshold_check})) { return &{$self->{closure_custom_threshold_check}}($self, %options); } my $unkn = defined($self->{threshold_unkn}) ? $self->{threshold_unkn} : 'unknown-' . $self->{thlabel}; my $warn = defined($self->{threshold_warn}) ? $self->{threshold_warn} : 'warning-' . $self->{thlabel}; my $crit = defined($self->{threshold_crit}) ? $self->{threshold_crit} : 'critical-' . $self->{thlabel}; my $value = ''; if (defined($self->{threshold_use})) { $value = $self->{result_values}->{ $self->{threshold_use} }; } else { $value = defined($self->{key_values}->[0]) ? $self->{result_values}->{ $self->{key_values}->[0]->{name} } : ''; } return $self->{perfdata}->threshold_check( value => $value, threshold => [ { label => $crit, exit_litteral => 'critical' }, { label => $warn, exit_litteral => 'warning' }, { label => $unkn, exit_litteral => 'unknown' } ] ); } sub output_error { my ($self, %options) = @_; return sprintf($self->{output_error_template}, $self->{error_msg}); } sub output { my ($self, %options) = @_; if (defined($self->{closure_custom_output})) { return $self->{closure_custom_output}->($self); } my ($value, $unit, $name) = ('', ''); if (defined($self->{output_use})) { $name = $self->{output_use}; } else { $name = defined($self->{key_values}->[0]) ? $self->{key_values}->[0]->{name} : undef; } if (defined($name)) { $value = $self->{result_values}->{$name}; if ($self->{output_change_bytes} == 1) { ($value, $unit) = $self->{perfdata}->change_bytes(value => $value); } elsif ($self->{output_change_bytes} == 2) { ($value, $unit) = $self->{perfdata}->change_bytes(value => $value, network => 1); } } return sprintf($self->{output_template}, $value, $unit); } sub use_instances { my ($self, %options) = @_; if (!defined($options{extra_instance}) || $options{extra_instance} != 0 || $self->{output}->use_new_perfdata()) { return 1; } return 0; } sub perfdata { my ($self, %options) = @_; if (defined($self->{closure_custom_perfdata})) { return &{$self->{closure_custom_perfdata}}($self, %options); } my $warn = defined($self->{threshold_warn}) ? $self->{threshold_warn} : 'warning-' . $self->{thlabel}; my $crit = defined($self->{threshold_crit}) ? $self->{threshold_crit} : 'critical-' . $self->{thlabel}; foreach my $perf (@{$self->{perfdatas}}) { my ($label, $extra_label, $min, $max, $th_total) = ($self->{label}, ''); my $cast_int = (defined($perf->{cast_int}) && $perf->{cast_int} == 1) ? 1 : 0; my $template = '%s'; $template = $perf->{template} if (defined($perf->{template})); $label = $perf->{label} if (defined($perf->{label})); if (defined($perf->{min})) { $min = ($perf->{min} =~ /[^0-9.-]/) ? $self->{result_values}->{$perf->{min}} : $perf->{min}; } if (defined($perf->{max})) { $max = ($perf->{max} =~ /[^0-9.-]/) ? $self->{result_values}->{$perf->{max}} : $perf->{max}; } if (defined($perf->{threshold_total})) { $th_total = ($perf->{threshold_total} =~ /[^0-9.-]/) ? $self->{result_values}->{$perf->{threshold_total}} : $perf->{threshold_total}; } my $instances; if (defined($perf->{label_extra_instance}) && $perf->{label_extra_instance} == 1) { my $instance = ''; if (defined($perf->{instance_use})) { $instance = $self->{result_values}->{$perf->{instance_use}}; } else { $instance = $self->{instance}; } if (!defined($options{extra_instance}) || $options{extra_instance} != 0 || $self->{output}->use_new_perfdata()) { $instances = $instance; } } my $value = defined($perf->{value}) ? $perf->{value} : $self->{key_values}->[0]->{name}; $self->{output}->perfdata_add( label => $label, instances => $instances, nlabel => $self->{nlabel}, unit => $perf->{unit}, value => $cast_int == 1 ? int($self->{result_values}->{$value}) : sprintf($template, $self->{result_values}->{$value}), warning => $self->{perfdata}->get_perfdata_for_output(label => $warn, total => $th_total, cast_int => $cast_int), critical => $self->{perfdata}->get_perfdata_for_output(label => $crit, total => $th_total, cast_int => $cast_int), min => $min, max => $max ); } } sub eval { my ($self, %options) = @_; if ($self->{safe_test} == 0) { my ($code) = centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'Safe', no_quit => 1 ); if ($code == 0) { $self->{safe} = Safe->new(); $self->{safe}->share('$values'); } $self->{safe_test} = 1; } my $result; if (defined($self->{safe})) { our $values = $self->{result_values}; $result = $self->{safe}->reval($options{value}, 1); if ($@) { die 'Unsafe code evaluation: ' . $@; } } else { my $values = $self->{result_values}; { local $SIG{__WARN__} = sub {}; # ignore $result = eval "$options{value}"; if ($@) { die 'Code evaluation error: ' . $@; } } } return $result; } sub execute { my ($self, %options) = @_; my $old_datas = {}; $self->{result_values} = {}, $self->{error_msg} = undef; my $quit = 0; $options{new_datas} = {} if (!defined($options{new_datas})); foreach my $value (@{$self->{key_values}}) { if (!defined($options{values}->{$value->{name}}) || defined($value->{no_value}) && $options{values}->{$value->{name}} eq $value->{no_value}) { $quit = 2; last; } if ((defined($value->{diff}) && $value->{diff} == 1) || (defined($value->{per_minute}) && $value->{per_minute} == 1) || (defined($value->{per_second}) && $value->{per_second} == 1)) { $options{new_datas}->{$self->{instance} . '_' . $value->{name}} = $options{values}->{$value->{name}}; $old_datas->{$self->{instance} . '_' . $value->{name}} = $self->{statefile}->get(name => $self->{instance} . '_' . $value->{name}); if (!defined($old_datas->{$self->{instance} . '_' . $value->{name}})) { $quit = 1; next; } if ($old_datas->{$self->{instance} . '_' . $value->{name}} > $options{new_datas}->{$self->{instance} . '_' . $value->{name}}) { $old_datas->{$self->{instance} . '_' . $value->{name}} = 0; } } else { $options{new_datas}->{$self->{instance} . '_' . $value->{name}} = $options{values}->{$value->{name}}; if (defined($self->{statefile})) { $old_datas->{$self->{instance} . '_' . $value->{name}} = $self->{statefile}->get(name => $self->{instance} . '_' . $value->{name}); } } } # Very manual if ($self->{manual_keys} == 1) { foreach my $name (keys %{$options{values}}) { $options{new_datas}->{$self->{instance} . '_' . $name} = $options{values}->{$name}; if (defined($self->{statefile})) { $old_datas->{$self->{instance} . '_' . $name} = $self->{statefile}->get(name => $self->{instance} . '_' . $name); } } } if ($quit == 2) { $self->{error_msg} = 'skipped (no value(s))'; return -10; } if (defined($self->{statefile})) { $self->{last_timestamp} = $self->{statefile}->get(name => 'last_timestamp'); } if ($quit == 1) { $self->{error_msg} = 'Buffer creation'; return -1; } my $delta_time; if (defined($self->{statefile}) && defined($self->{last_timestamp})) { $delta_time = $options{new_datas}->{last_timestamp} - $self->{last_timestamp}; if ($delta_time <= 0) { $delta_time = 1; } } if (defined($self->{closure_custom_calc})) { return $self->{closure_custom_calc}->( $self, old_datas => $old_datas, new_datas => $options{new_datas}, delta_time => $delta_time, extra_options => $self->{closure_custom_calc_extra_options} ); } return $self->calc(old_datas => $old_datas, new_datas => $options{new_datas}, delta_time => $delta_time); } 1; CENTREON_PLUGINS_VALUES $fatpacked{"os/linux/local/custom/cli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_CUSTOM_CLI'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::custom::cli; use strict; use warnings; use centreon::plugins::ssh; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; if (!defined($options{output})) { print "Class Custom: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); $options{output}->option_exit(); } $self->{mode_name} = $options{mode_name}; # discovery-snmp cannot be used at distance. return $self if (defined($options{mode_name}) && $options{mode_name} =~ /discovery-snmp|check-plugin/); if (!defined($options{noptions})) { $options{options}->add_options(arguments => { 'hostname:s' => { name => 'hostname' }, 'timeout:s' => { name => 'timeout' }, 'command:s' => { name => 'command' }, 'command-path:s' => { name => 'command_path' }, 'command-options:s' => { name => 'command_options' }, 'sudo:s' => { name => 'sudo' } }); } $options{options}->add_help(package => __PACKAGE__, sections => 'CLI OPTIONS', once => 1); $self->{output} = $options{output}; $self->{ssh} = centreon::plugins::ssh->new(%options); return $self; } sub set_options { my ($self, %options) = @_; $self->{option_results} = $options{option_results}; } sub set_defaults {} sub check_options { my ($self, %options) = @_; return 0 if ($self->{mode_name} =~ /discovery-snmp|check-plugin/); if (defined($self->{option_results}->{timeout}) && $self->{option_results}->{timeout} =~ /(\d+)/) { $self->{timeout} = $1; } if (defined($self->{option_results}->{hostname}) && $self->{option_results}->{hostname} ne '') { $self->{ssh}->check_options(option_results => $self->{option_results}); } centreon::plugins::misc::check_security_command( output => $self->{output}, command => $self->{option_results}->{command}, command_options => $self->{option_results}->{command_options}, command_path => $self->{option_results}->{command_path} ); return 0; } sub get_identifier { my ($self, %options) = @_; my $id = defined($self->{option_results}->{hostname}) ? $self->{option_results}->{hostname} : 'me'; if (defined($self->{option_results}->{hostname}) && $self->{option_results}->{hostname} ne '') { $id .= ':' . $self->{ssh}->get_port(); } return $id; } sub execute_command { my ($self, %options) = @_; centreon::plugins::misc::check_security_command( output => $self->{output}, command => $self->{option_results}->{command}, command_options => $self->{option_results}->{command_options}, command_path => $self->{option_results}->{command_path} ); my $timeout = $self->{timeout}; if (!defined($timeout)) { $timeout = defined($options{timeout}) ? $options{timeout} : 45; } my $command_options = defined($self->{option_results}->{command_options}) && $self->{option_results}->{command_options} ne '' ? $self->{option_results}->{command_options} : $options{command_options}; if (defined($options{command_options_suffix})) { $command_options .= $options{command_options_suffix}; } my ($stdout, $exit_code); if (defined($self->{option_results}->{hostname}) && $self->{option_results}->{hostname} ne '') { ($stdout, $exit_code) = $self->{ssh}->execute( hostname => $self->{option_results}->{hostname}, sudo => $self->{option_results}->{sudo}, command => defined($self->{option_results}->{command}) && $self->{option_results}->{command} ne '' ? $self->{option_results}->{command} : $options{command}, command_path => defined($self->{option_results}->{command_path}) && $self->{option_results}->{command_path} ne '' ? $self->{option_results}->{command_path} : $options{command_path}, command_options => $command_options, timeout => $timeout, no_quit => $options{no_quit} ); } else { ($stdout, $exit_code) = centreon::plugins::misc::execute( output => $self->{output}, sudo => $self->{option_results}->{sudo}, options => { timeout => $timeout }, command => defined($self->{option_results}->{command}) && $self->{option_results}->{command} ne '' ? $self->{option_results}->{command} : $options{command}, command_path => defined($self->{option_results}->{command_path}) && $self->{option_results}->{command_path} ne '' ? $self->{option_results}->{command_path} : $options{command_path}, command_options => $command_options, no_quit => $options{no_quit} ); } $self->{output}->output_add(long_msg => "command response: $stdout", debug => 1); return ($stdout, $exit_code); } 1; =head1 NAME ssh =head1 SYNOPSIS my ssh =head1 CLI OPTIONS =over 8 =item B<--hostname> Hostname to query. =item B<--timeout> Timeout in seconds for the command (default: 45). Default value can be override by the mode. =item B<--command> Command to get information. Used it you have output in a file. =item B<--command-path> Command path. =item B<--command-options> Command options. =item B<--sudo> sudo command. =back =head1 DESCRIPTION B<custom>. =cut OS_LINUX_LOCAL_CUSTOM_CLI $fatpacked{"os/linux/local/mode/checkplugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_CHECKPLUGIN'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::checkplugin; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); use Time::HiRes qw(gettimeofday tv_interval); use centreon::plugins::ssh; use centreon::plugins::misc; sub custom_status_output { my ($self, %options) = @_; return $self->{result_values}->{short_message}; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'commands', type => 1, message_separator => ' - ', display_long => 0 } ]; $self->{maps_counters}->{commands} = [ { label => 'status', type => 2, unknown_default => '%{exit_code} == 3', warning_default => '%{exit_code} == 1', critical_default => '%{exit_code} == 2', set => { key_values => [ { name => 'short_message' }, { name => 'exit_code' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } }, { label => 'time', nlabel => 'ssh.response.time.seconds', display_ok => 0, set => { key_values => [ { name => 'time' } ], output_template => 'response time: %.3fs', perfdatas => [ { template => '%.3f', min => 0, unit => 's', label_extra_instance => 1 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'hostname:s' => { name => 'hostname' }, 'timeout:s' => { name => 'timeout' }, 'command:s@' => { name => 'command' } }); $self->{ssh} = centreon::plugins::ssh->new(%options); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); if (!defined($self->{option_results}->{hostname}) || $self->{option_results}->{hostname} eq '') { $self->{output}->add_option_msg(short_msg => 'Set --hostname option'); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{command})) { $self->{output}->add_option_msg(short_msg => 'Need to specify at least one --command option'); $self->{output}->option_exit(); } $self->{ssh}->check_options(option_results => $self->{option_results}); } sub parse_perfdatas { my ($self, %options) = @_; while ($options{perfdatas} =~ /(.*?)=([0-9\.]+)([^0-9;]+?)?([0-9.@;:]+?)?(?:\s+|\Z)/g) { my ($label, $value, $unit, $extra) = ($1, $2, $3, $4); $label = centreon::plugins::misc::trim($label); $label =~ s/^'//; $label =~ s/'$//; my @extras = split(';', $extra); $self->{output}->perfdata_add( nlabel => $label, unit => $unit, value => $value, warning => $extras[1], critical => $extras[2], min => $extras[3], max => $extras[4] ); } } sub parse_plugin_output { my ($self, %options) = @_; my @lines = split(/\n/, $options{output}); my $short = 'no output'; my $line = shift(@lines); if (defined($line) && $line =~ /^(.*?)(?:\|(.*)|\Z)/) { $short = $1; if (defined($2)) { $self->parse_perfdatas(perfdatas => $2); } } $self->{commands}->{ $options{cmd} }->{short_message} = $short; foreach (@lines) { $self->{output}->output_add(long_msg => $_); } } sub manage_selection { my ($self, %options) = @_; $self->{output}->set_ignore_label(); my $timeout = $self->{option_results}->{timeout}; $timeout = 45 if (!defined($timeout) || $timeout !~ /\d+/); $self->{commands} = {}; my $i = 1; foreach my $command (@{$self->{option_results}->{command}}) { my $timing0 = [gettimeofday]; my ($stdout, $exit_code) = $self->{ssh}->execute( hostname => $self->{option_results}->{hostname}, command => $command, timeout => $timeout, no_quit => 1 ); my $cmd = 'command' . $i; $self->{commands}->{$cmd} = { time => tv_interval($timing0, [gettimeofday]), exit_code => $exit_code }; $self->parse_plugin_output(cmd => $cmd, output => $stdout); $i++; } } 1; =head1 MODE SSH execution commands in a remote host. =over 8 =item B<--hostname> Hostname to query. =item B<--timeout> Timeout in seconds for the command (default: 45). =item B<--command> command to execute on the remote machine =item B<--unknown-status> Define the conditions to match for the status to be UNKNOWN (default: '%{exit_code} == 3'). You can use the following variables: %{short_message}, %{exit_code} =item B<--warning-status> Define the conditions to match for the status to be WARNING (default: '%{exit_code} == 1'). You can use the following variables: %{short_message}, %{exit_code} =item B<--critical-status> Define the conditions to match for the status to be CRITICAL (default: '%{exit_code} == 2'). You can use the following variables: %{short_message}, %{exit_code} =item B<--warning-time> Warning threshold in seconds. =item B<--critical-time> Critical threshold in seconds. =back =cut OS_LINUX_LOCAL_MODE_CHECKPLUGIN $fatpacked{"os/linux/local/mode/cmdreturn.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_CMDRETURN'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::cmdreturn; use base qw(centreon::plugins::mode); use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'exec-command:s' => { name => 'exec_command' }, 'exec-command-path:s' => { name => 'exec_command_path' }, 'exec-command-options:s' => { name => 'exec_command_options' }, 'manage-returns:s' => { name => 'manage_returns', default => '' }, 'separator:s' => { name => 'separator', default => '#' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (!defined($self->{option_results}->{exec_command})) { $self->{output}->add_option_msg(short_msg => "Need to specify exec-command option."); $self->{output}->option_exit(); } $self->{expressions} = []; foreach my $entry (split(/$self->{option_results}->{separator}/, $self->{option_results}->{manage_returns})) { next if (!($entry =~ /(.*?),(.*?),(.*)/)); next if (!$self->{output}->is_litteral_status(status => $2)); my ($expr, $rv, $msg) = ($1, $2, $3); if ($expr ne '') { if ($expr =~ /^\s*([0-9]+)\s*$/) { push @{$self->{expressions}}, { test => "%(code) == $1", rv => $rv, msg => $msg }; } else { push @{$self->{expressions}}, { test => $expr, rv => $rv, msg => $msg }; } } else { $self->{expression_default} = { rv => $rv, msg => $msg }; } } if ($self->{option_results}->{manage_returns} eq '' || (scalar(@{$self->{expressions}}) == 0 && !defined($self->{expression_default}))) { $self->{output}->add_option_msg(short_msg => "Need to specify manage-returns option correctly."); $self->{output}->option_exit(); } for (my $i = 0; $i < scalar(@{$self->{expressions}}); $i++) { $self->{expressions}->[$i]->{test} =~ s/%\{(.*?)\}/\$values->{$1}/g; $self->{expressions}->[$i]->{test} =~ s/%\((.*?)\)/\$values->{$1}/g; } centreon::plugins::misc::check_security_whitelist( output => $self->{output}, command => $self->{option_results}->{exec_command}, command_path => $self->{option_results}->{exec_command_path}, command_options => $self->{option_results}->{exec_command_options} ); } sub run { my ($self, %options) = @_; my ($stdout, $exit_code) = $options{custom}->execute_command( command => $self->{option_results}->{exec_command}, command_path => $self->{option_results}->{exec_command_path}, command_options => $self->{option_results}->{exec_command_options}, no_quit => 1 ); my $long_msg = $stdout; $long_msg =~ s/\|/~/mg; $self->{output}->output_add(long_msg => $long_msg); my $matched = 0; my $values = { code => $exit_code, output => $stdout }; foreach (@{$self->{expressions}}) { if ($self->{output}->test_eval(test => $_->{test}, values => $values)) { $self->{output}->output_add( severity => $_->{rv}, short_msg => $_->{msg} ); $matched = 1; last; } } if ($matched == 0 && defined($self->{expression_default})) { $self->{output}->output_add( severity => $self->{expression_default}->{rv}, short_msg => $self->{expression_default}->{msg} ); } elsif ($matched == 0) { $self->{output}->output_add( severity => 'UNKNOWN', short_msg => "Command exit code ($exit_code)" ); } if (defined($exit_code)) { $self->{output}->perfdata_add( nlabel => 'command.exit.code.count', value => $exit_code ); } $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check command returns. =over 8 =item B<--manage-returns> Set action according command exit code. Example: %(code) == 0,OK,File xxx exist#%(code) == 1,CRITICAL,File xxx not exist#,UNKNOWN,Command problem =item B<--separator> Set the separator used in --manage-returns (default : #) =item B<--exec-command> Command to test (default: none). You can use 'sh' to use '&&' or '||'. =item B<--exec-command-path> Command path (default: none). =item B<--exec-command-options> Command options (default: none). =back =cut OS_LINUX_LOCAL_MODE_CMDRETURN $fatpacked{"os/linux/local/mode/connections.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_CONNECTIONS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::connections; use base qw(centreon::plugins::mode); use strict; use warnings; my %map_ss_states = ( UNCONN => 'closed', LISTEN => 'listen', 'SYN-SENT' => 'synSent', 'SYN-RECV' => 'synReceived', ESTAB => 'established', 'FIN-WAIT-1' => 'finWait1', 'FIN-WAIT-2' => 'finWait2', 'CLOSE-WAIT' => 'closeWait', 'LAST-ACK' => 'lastAck', CLOSING => 'closing', 'TIME-WAIT' => 'timeWait', UNKNOWN => 'unknown', ); my %map_states = ( CLOSED => 'closed', LISTEN => 'listen', SYN_SENT => 'synSent', SYN_RECV => 'synReceived', ESTABLISHED => 'established', FIN_WAIT1 => 'finWait1', FIN_WAIT2 => 'finWait2', CLOSE_WAIT => 'closeWait', LAST_ACK => 'lastAck', CLOSING => 'closing', TIME_WAIT => 'timeWait', UNKNOWN => 'unknown', ); sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'warning:s' => { name => 'warning', }, 'critical:s' => { name => 'critical', }, 'service:s@' => { name => 'service', }, 'application:s@' => { name => 'application', }, 'con-mode:s' => { name => 'con_mode', default => 'netstat' } }); $self->{connections} = []; $self->{services} = { total => { filter => '(?!(udp*))#.*?#.*?#.*?#.*?#(?!(listen))', builtin => 1, number => 0, msg => 'Total connections: %d' } }; $self->{applications} = {}; $self->{states} = { closed => 0, listen => 0, synSent => 0, synReceived => 0, established => 0, finWait1 => 0, finWait2 => 0, closeWait => 0, lastAck => 0, closing => 0, timeWait => 0 }; return $self; } sub netstat_build { my ($self, %options) = @_; foreach my $line (split /\n/, $self->{stdout}) { next if ($line !~ /^(tcp|udp|tcp6|udp6)\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s*(\S*)/); my ($type, $src, $dst, $state) = ($1, $2, $3, $4); $src =~ /(.*):(\d+|\*)$/; my ($src_addr, $src_port) = ($1, $2); $dst =~ /(.*):(\d+|\*)$/; my ($dst_addr, $dst_port) = ($1, $2); if ($type =~ /^udp/) { if ($dst_port eq '*') { $state = 'listen'; } else { $state = 'established'; } } else { $state = $map_states{$state}; $self->{states}->{$state}++; } push @{$self->{connections}}, $type . "#$src_addr#$src_port#$dst_addr#$dst_port#" . lc($state); } } sub ss_build { my ($self, %options) = @_; foreach my $line (split /\n/, $self->{stdout}) { next if ($line !~ /^(tcp|udp)\s+(\S+)\s+\S+\s+\S+\s+(\S+)\s*(\S+)/); my ($type, $src, $dst, $state) = ($1, $3, $4, $2); $src =~ /(.*):(\d+|\*)$/; my ($src_addr, $src_port) = ($1, $2); $dst =~ /(.*):(\d+|\*)$/; my ($dst_addr, $dst_port) = ($1, $2); $type .= '6' if ($src_addr !~ /^\d+\.\d+\.\d+\.\d+$/); if ($type =~ /^udp/) { if ($dst_port eq '*') { $state = 'listen'; } else { $state = 'established'; } } else { $state = $map_ss_states{$state}; $self->{states}->{$state}++; } push @{$self->{connections}}, $type . "#$src_addr#$src_port#$dst_addr#$dst_port#" . lc($state); } } sub build_connections { my ($self, %options) = @_; if ($self->{option_results}->{con_mode} !~ /^ss|netstat$/) { $self->{output}->add_option_msg(short_msg => "Unknown --con-mode option."); $self->{output}->option_exit(); } my ($command, $command_options) = ('netstat', '-antu 2>&1'); if ($self->{option_results}->{con_mode} eq 'ss') { $command = 'ss'; $command_options = '-a -A tcp,udp -n 2>&1'; } ($self->{stdout}) = $options{custom}->execute_command( command => $command, command_options => $command_options ); if ($self->{option_results}->{con_mode} eq 'ss') { $self->ss_build(); } else { $self->netstat_build(); } } sub check_services { my ($self, %options) = @_; foreach my $service (@{$self->{option_results}->{service}}) { my ($tag, $ipv, $state, $port_src, $port_dst, $filter_ip_src, $filter_ip_dst, $warn, $crit) = split /,/, $service; if (!defined($tag) || $tag eq '') { $self->{output}->add_option_msg(short_msg => "Tag for service '" . $service . "' must be defined."); $self->{output}->option_exit(); } if (defined($self->{services}->{$tag})) { $self->{output}->add_option_msg(short_msg => "Tag '" . $tag . "' (service) already exists."); $self->{output}->option_exit(); } $self->{services}->{$tag} = { filter => ((defined($ipv) && $ipv ne '') ? $ipv : '.*?') . '#' . ((defined($filter_ip_src) && $filter_ip_src ne '') ? $filter_ip_src : '.*?') . '#' . ((defined($port_src) && $port_src ne '') ? $port_src : '.*?') . '#' . ((defined($filter_ip_dst) && $filter_ip_dst ne '') ? $filter_ip_dst : '.*?') . '#' . ((defined($port_dst) && $port_dst ne '') ? $port_dst : '.*?') . '#' . ((defined($state) && $state ne '') ? lc($state) : '(?!(listen))'), builtin => 0, number => 0 }; if (($self->{perfdata}->threshold_validate(label => 'warning-service-' . $tag, value => $warn)) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $warn . "' for service '$tag'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical-service-' . $tag, value => $crit)) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $crit . "' for service '$tag'."); $self->{output}->option_exit(); } } } sub check_applications { my ($self, %options) = @_; foreach my $app (@{$self->{option_results}->{application}}) { my ($tag, $services, $warn, $crit) = split /,/, $app; if (!defined($tag) || $tag eq '') { $self->{output}->add_option_msg(short_msg => "Tag for application '" . $app . "' must be defined."); $self->{output}->option_exit(); } if (defined($self->{applications}->{$tag})) { $self->{output}->add_option_msg(short_msg => "Tag '" . $tag . "' (application) already exists."); $self->{output}->option_exit(); } $self->{applications}->{$tag} = { services => {} }; foreach my $service (split /\|/, $services) { if (!defined($self->{services}->{$service})) { $self->{output}->add_option_msg(short_msg => "Service '" . $service . "' is not defined."); $self->{output}->option_exit(); } $self->{applications}->{$tag}->{services}->{$service} = 1; } if (($self->{perfdata}->threshold_validate(label => 'warning-app-' . $tag, value => $warn)) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $warn . "' for application '$tag'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical-app-' . $tag, value => $crit)) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $crit . "' for application '$tag'."); $self->{output}->option_exit(); } } } sub test_services { my ($self, %options) = @_; foreach my $tag (keys %{$self->{services}}) { foreach (@{$self->{connections}}) { if (/$self->{services}->{$tag}->{filter}/) { $self->{services}->{$tag}->{number}++; } } my $exit_code = $self->{perfdata}->threshold_check( value => $self->{services}->{$tag}->{number}, threshold => [ { label => 'critical-service-' . $tag, 'exit_litteral' => 'critical' }, { label => 'warning-service-' . $tag, exit_litteral => 'warning' } ] ); my ($perf_label, $msg) = ('service_' . $tag, "Service '$tag' connections: %d"); if ($self->{services}->{$tag}->{builtin} == 1) { ($perf_label, $msg) = ($tag, $self->{services}->{$tag}->{msg}); } $self->{output}->output_add( severity => $exit_code, short_msg => sprintf($msg, $self->{services}->{$tag}->{number}) ); $self->{output}->perfdata_add( label => $perf_label, value => $self->{services}->{$tag}->{number}, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-service-' . $tag), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-service-' . $tag), min => 0 ); } } sub test_applications { my ($self, %options) = @_; foreach my $tag (keys %{$self->{applications}}) { my $number = 0; foreach (keys %{$self->{applications}->{$tag}->{services}}) { $number += $self->{services}->{$_}->{number}; } my $exit_code = $self->{perfdata}->threshold_check( value => $number, threshold => [ { label => 'critical-app-' . $tag, 'exit_litteral' => 'critical' }, { label => 'warning-app-' . $tag, exit_litteral => 'warning' } ] ); $self->{output}->output_add( severity => $exit_code, short_msg => sprintf("Applicatin '%s' connections: %d", $tag, $number) ); $self->{output}->perfdata_add( label => 'app_' . $tag, value => $number, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-app-' . $tag), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-app-' . $tag), min => 0 ); } } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (($self->{perfdata}->threshold_validate(label => 'warning-service-total', value => $self->{option_results}->{warning})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $self->{option_results}->{warning} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical-service-total', value => $self->{option_results}->{critical})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $self->{option_results}->{critical} . "'."); $self->{output}->option_exit(); } $self->check_services(); $self->check_applications(); } sub run { my ($self, %options) = @_; $self->build_connections(custom => $options{custom}); $self->test_services(); $self->test_applications(); foreach (keys %{$self->{states}}) { $self->{output}->perfdata_add( label => 'con_' . $_, value => $self->{states}->{$_}, min => 0 ); } $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check tcp/udp connections (udp connections are not in total. Use option '--service' to check it). 'ipx', 'x25' connections are not checked (need output to do it. If you have, you can post it in github :) Command used: 'netstat -antu 2>&1' or 'ss -a -A tcp,udp -n 2>&1' =over 8 =item B<--warning> Warning threshold for total connections. =item B<--critical> Critical threshold for total connections. =item B<--service> Check tcp connections following rules: tag,[type],[state],[port-src],[port-dst],[filter-ip-src],[filter-ip-dst],[threshold-warning],[threshold-critical] Example to test SSH connections on the server: --service="ssh,,,22,,,,10,20" =over 16 =item <tag> Name to identify service (must be unique and couldn't be 'total'). =item <type> regexp - can use 'ipv4', 'ipv6', 'udp', 'udp6'. Empty means all. =item <state> regexp - can use 'finWait1', 'established',... Empty means all (minus listen). For udp connections, there are 'established' and 'listen'. =item <filter-ip-*> regexp - can use to exclude or include some IPs. =item <threshold-*> nagios-perfdata - number of connections. =back =item B<--application> Check tcp connections of mutiple services: tag,[services],[threshold-warning],[threshold-critical] Example: --application="web,http|https,100,200" =over 16 =item <tag> Name to identify application (must be unique). =item <services> List of services (used the tag name. Separated by '|'). =item <threshold-*> nagios-perfdata - number of connections. =back =item B<--con-mode> Default mode for parsing and command: 'netstat' (default) or 'ss'. =back =cut OS_LINUX_LOCAL_MODE_CONNECTIONS $fatpacked{"os/linux/local/mode/cpu.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_CPU'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::cpu; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use Digest::MD5 qw(md5_hex); sub custom_cpu_avg_calc { my ($self, %options) = @_; my ($skipped, $buffer) = (1, 1); my ($count, $total_cpu) = (0, 0); foreach (keys %{$options{new_datas}}) { if (/^(.*?cpu\d+)_idle/) { my $prefix = $1; $skipped = 0; next if (!defined($options{old_datas}->{$_})); $buffer = 0; my ($old_total, $old_cpu_idle) = (0, 0); if ($options{new_datas}->{$_} >= $options{old_datas}->{$_}) { $old_total = $options{old_datas}->{$_} + $options{old_datas}->{$prefix . '_system'} + $options{old_datas}->{$prefix . '_user'} + $options{old_datas}->{$prefix . '_iowait'}; $old_cpu_idle = $options{old_datas}->{$_}; } my $total_elapsed = ($options{new_datas}->{$_} + $options{new_datas}->{$prefix . '_system'} + $options{new_datas}->{$prefix . '_user'} + $options{new_datas}->{$prefix . '_iowait'}) - $old_total; if ($total_elapsed == 0) { $self->{error_msg} = 'no new values for cpu counters'; return -12; } my $idle_elapsed = $options{new_datas}->{$_} - $old_cpu_idle; $total_cpu += 100 - (100 * $idle_elapsed / $total_elapsed); $count++; } } return -10 if ($skipped == 1); if ($buffer == 1) { $self->{error_msg} = "Buffer creation"; return -1; } $self->{result_values}->{prct_used} = $total_cpu / $count; return 0; } sub custom_cpu_core_calc { my ($self, %options) = @_; my ($old_total, $old_cpu_idle) = (0, 0); if ($options{new_datas}->{$self->{instance} . '_idle'} >= $options{old_datas}->{$self->{instance} . '_idle'}) { $old_total = $options{old_datas}->{$self->{instance} . '_idle'} + $options{old_datas}->{$self->{instance} . '_system'} + $options{old_datas}->{$self->{instance} . '_user'} + $options{old_datas}->{$self->{instance} . '_iowait'}; $old_cpu_idle = $options{old_datas}->{$self->{instance} . '_idle'}; } my $total_elapsed = ($options{new_datas}->{$self->{instance} . '_idle'} + $options{new_datas}->{$self->{instance} . '_system'} + $options{new_datas}->{$self->{instance} . '_user'} + $options{new_datas}->{$self->{instance} . '_iowait'}) - $old_total; if ($total_elapsed == 0) { $self->{error_msg} = 'no new values for cpu counters'; return -12; } my $idle_elapsed = $options{new_datas}->{$self->{instance} . '_idle'} - $old_cpu_idle; $self->{result_values}->{prct_used} = 100 - (100 * $idle_elapsed / $total_elapsed); return 0; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'cpu_avg', type => 0 }, { name => 'cpu_core', type => 1, cb_prefix_output => 'prefix_cpu_core_output' } ]; $self->{maps_counters}->{cpu_avg} = [ { label => 'average', nlabel => 'cpu.utilization.percentage', set => { key_values => [], closure_custom_calc => $self->can('custom_cpu_avg_calc'), manual_keys => 1, output_template => 'CPU(s) average usage is %.2f %%', output_use => 'prct_used', threshold_use => 'prct_used', perfdatas => [ { value => 'prct_used', template => '%.2f', min => 0, max => 100, unit => '%' } ] } } ]; $self->{maps_counters}->{cpu_core} = [ { label => 'core', nlabel => 'core.cpu.utilization.percentage', set => { key_values => [ { name => 'idle', diff => 1 }, { name => 'user', diff => 1 }, { name => 'system', diff => 1 }, { name => 'iowait', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_cpu_core_calc'), output_template => 'usage : %.2f %%', output_use => 'prct_used', threshold_use => 'prct_used', perfdatas => [ { value => 'prct_used', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1 } ] } } ]; } sub prefix_cpu_core_output { my ($self, %options) = @_; return "CPU '" . $options{instance_value}->{display} . "' "; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/stat 2>&1' ); $self->{cpu_avg} = {}; $self->{cpu_core} = {}; foreach (split(/\n/, $stdout)) { next if (!/cpu(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/); my $cpu_number = $1; $self->{cpu_core}->{$cpu_number} = { display => $cpu_number, idle => $5, system => $4, user => $2, iowait => $6 }; $self->{cpu_avg}->{'cpu' . $cpu_number . '_idle'} = $5; $self->{cpu_avg}->{'cpu' . $cpu_number . '_system'} = $4; $self->{cpu_avg}->{'cpu' . $cpu_number . '_user'} = $2; $self->{cpu_avg}->{'cpu' . $cpu_number . '_iowait'} = $6; } $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')); } 1; =head1 MODE Check system CPUs (need '/proc/stat' file). Command used: cat /proc/stat 2>&1 =over 8 =item B<--warning-average> Warning threshold average CPU utilization. =item B<--critical-average> Critical threshold average CPU utilization. =item B<--warning-core> Warning thresholds for each CPU core =item B<--critical-core> Critical thresholds for each CPU core =back =cut OS_LINUX_LOCAL_MODE_CPU $fatpacked{"os/linux/local/mode/cpudetailed.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_CPUDETAILED'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::cpudetailed; use base qw(centreon::plugins::mode); use strict; use warnings; use centreon::plugins::statefile; my $maps = [ { counter => 'user', output => 'User %.2f %%', position => 1 }, { counter => 'nice', output => 'Nice %.2f %%', position => 2 }, { counter => 'system', output => 'System %.2f %%', position => 3 }, { counter => 'idle', output => 'Idle %.2f %%', position => 4 }, { counter => 'wait', output => 'Wait %.2f %%', position => 5 }, { counter => 'interrupt', output => 'Interrupt %.2f %%', position => 6 }, { counter => 'softirq', output => 'Soft Irq %.2f %%', position => 7 }, { counter => 'steal', output => 'Steal %.2f %%', position => 8 }, { counter => 'guest', output => 'Guest %.2f %%', position => 9 }, { counter => 'guestnice', output => 'Guest Nice %.2f %%', position => 10 } ]; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { }); foreach (@{$maps}) { $options{options}->add_options(arguments => { 'warning-' . $_->{counter} . ':s' => { name => 'warning_' . $_->{counter} }, 'critical-' . $_->{counter} . ':s' => { name => 'critical_' . $_->{counter} } }); } $self->{statefile_cache} = centreon::plugins::statefile->new(%options); $self->{hostname} = undef; return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); foreach (@{$maps}) { if (($self->{perfdata}->threshold_validate(label => 'warning-' . $_->{counter}, value => $self->{option_results}->{'warning_' . $_->{counter}})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning-" . $_->{counter} . " threshold '" . $self->{option_results}->{'warning_' . $_->{counter}} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical-' . $_->{counter}, value => $self->{option_results}->{'critical_' . $_->{counter}})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical-" . $_->{counter} . " threshold '" . $self->{option_results}->{'critical_' . $_->{counter}} . "'."); $self->{output}->option_exit(); } } $self->{statefile_cache}->check_options(%options); $self->{hostname} = $self->{option_results}->{hostname}; if (!defined($self->{hostname})) { $self->{hostname} = 'me'; } } sub run { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/stat 2>&1' ); $self->{statefile_cache}->read(statefile => 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode}); # Manage values my ($buffer_creation, $exit) = (0, 0); my $save_datas = {}; my $new_datas = {}; my $old_datas = {}; my ($total_datas, $total_cpu_num) = ({}, 0); foreach my $line (split(/\n/, $stdout)) { next if ($line !~ /cpu(\d+)\s+/); my $cpu_number = $1; my @values = split /\s+/, $line; foreach (@{$maps}) { next if (!defined($values[$_->{position}])); if (!defined($new_datas->{$cpu_number})) { $new_datas->{$cpu_number} = { total => 0 }; $old_datas->{$cpu_number} = { total => 0 }; } $new_datas->{$cpu_number}->{$_->{counter}} = $values[$_->{position}]; $save_datas->{'cpu' . $cpu_number . '_' . $_->{counter}} = $values[$_->{position}]; my $tmp_value = $self->{statefile_cache}->get(name => 'cpu' . $cpu_number . '_' . $_->{counter}); if (!defined($tmp_value)) { $buffer_creation = 1; next; } if ($new_datas->{$cpu_number}->{$_->{counter}} < $tmp_value) { $buffer_creation = 1; next; } $exit = 1; $old_datas->{$cpu_number}->{$_->{counter}} = $tmp_value; $new_datas->{$cpu_number}->{total} += $new_datas->{$cpu_number}->{$_->{counter}}; $old_datas->{$cpu_number}->{total} += $old_datas->{$cpu_number}->{$_->{counter}}; } } $self->{statefile_cache}->write(data => $save_datas); if ($buffer_creation == 1) { $self->{output}->output_add( severity => 'OK', short_msg => "Buffer creation..." ); if ($exit == 0) { $self->{output}->display(); $self->{output}->exit(); } } $self->{output}->output_add( severity => 'OK', short_msg => "CPUs usages are ok." ); foreach my $cpu_number (sort keys(%$new_datas)) { # In buffer creation. New cpu next if (scalar(keys %{$old_datas->{$cpu_number}}) <= 1); if ($new_datas->{$cpu_number}->{total} - $old_datas->{$cpu_number}->{total} == 0) { $self->{output}->output_add( severity => 'OK', short_msg => "Counter not moved. Have to wait." ); $self->{output}->display(); $self->{output}->exit(); } $total_cpu_num++; my @exits; foreach (@{$maps}) { next if (!defined($new_datas->{$cpu_number}->{$_->{counter}})); my $value = (($new_datas->{$cpu_number}->{$_->{counter}} - $old_datas->{$cpu_number}->{$_->{counter}}) * 100) / ($new_datas->{$cpu_number}->{total} - $old_datas->{$cpu_number}->{total}); push @exits, $self->{perfdata}->threshold_check(value => $value, threshold => [ { label => 'critical-' . $_->{counter}, 'exit_litteral' => 'critical' }, { label => 'warning-' . $_->{counter}, 'exit_litteral' => 'warning' }]); } $exit = $self->{output}->get_most_critical(status => [ @exits ]); my $str_output = "CPU '$cpu_number' Usage: "; my $str_append = ''; foreach (@{$maps}) { next if (!defined($new_datas->{$cpu_number}->{$_->{counter}})); my $value = (($new_datas->{$cpu_number}->{$_->{counter}} - $old_datas->{$cpu_number}->{$_->{counter}}) * 100) / ($new_datas->{$cpu_number}->{total} - $old_datas->{$cpu_number}->{total}); $total_datas->{$_->{counter}} = 0 if (!defined($total_datas->{$_->{counter}})); $total_datas->{$_->{counter}} += $value; $str_output .= $str_append . sprintf($_->{output}, $value); $str_append = ', '; my $warning = $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $_->{counter}); my $critical = $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $_->{counter}); $self->{output}->perfdata_add( nlabel => 'core.cpu.utilization.percentage', unit => '%', instances => [$cpu_number, $_->{counter}], value => sprintf("%.2f", $value), warning => $warning, critical => $critical, min => 0, max => 100 ); } $self->{output}->output_add(long_msg => $str_output); if (!$self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1)) { $self->{output}->output_add( severity => $exit, short_msg => $str_output ); } } # We can display a total (some buffer creation and counters have moved) if ($total_cpu_num != 0) { foreach my $counter (sort keys %{$total_datas}) { $self->{output}->perfdata_add( nlabel => 'cpu.utilization.percentage', instances => $counter, unit => '%', value => sprintf("%.2f", $total_datas->{$counter} / $total_cpu_num), min => 0, max => 100 ); } } $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check average usage for each CPUs (need '/proc/stat' file) (User, Nice, System, Idle, Wait, Interrupt, SoftIRQ, Steal, Guest, GuestNice) Command used: cat /proc/stat 2>&1 =over 8 =item B<--warning-*> Warning threshold in percent. Can be: 'user', 'nice', 'system', 'idle', 'wait', 'interrupt', 'softirq', 'steal', 'guest', 'guestnice'. =item B<--critical-*> Critical threshold in percent. Can be: 'user', 'nice', 'system', 'idle', 'wait', 'interrupt', 'softirq', 'steal', 'guest', 'guestnice'. =back =cut OS_LINUX_LOCAL_MODE_CPUDETAILED $fatpacked{"os/linux/local/mode/discoverysnmp.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_DISCOVERYSNMP'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::discoverysnmp; use base qw(centreon::plugins::mode); use strict; use warnings; use os::linux::local::mode::resources::discovery qw($discovery_match); use centreon::plugins::snmp; use NetAddr::IP; use JSON::XS; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'subnet:s' => { name => 'subnet' }, 'snmp-port:s' => { name => 'snmp_port', default => 161 }, 'snmp-version:s@' => { name => 'snmp_version' }, 'snmp-community:s@' => { name => 'snmp_community' }, 'snmp-timeout:s' => { name => 'snmp_timeout', default => 1 }, 'prettify' => { name => 'prettify' }, 'extra-oids:s' => { name => 'extra_oids' } }); $self->{snmp} = centreon::plugins::snmp->new(%options, noptions => 1); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (!defined($self->{option_results}->{subnet}) || $self->{option_results}->{subnet} !~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/) { $self->{output}->add_option_msg(short_msg => "Need to specify --subnet option (<ip>/<cidr>)."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{snmp_community}) || $self->{option_results}->{snmp_community} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify --snmp-community option."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{snmp_version}) || $self->{option_results}->{snmp_version} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify --snmp-version option."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{snmp_timeout}) || $self->{option_results}->{snmp_timeout} !~ /(\d+)/) { $self->{output}->add_option_msg(short_msg => "Need to specify --snmp-timeout option."); $self->{output}->option_exit(); } $self->{snmp}->set_snmp_connect_params(Timeout => $self->{option_results}->{snmp_timeout} * (10**6)); $self->{snmp}->set_snmp_connect_params(Retries => 0); $self->{snmp}->set_snmp_params(subsetleef => 1); $self->{snmp}->set_snmp_params(snmp_autoreduce => 0); $self->{snmp}->set_snmp_params(snmp_errors_exit => 'unknown'); $self->{oid_sysDescr} = '.1.3.6.1.2.1.1.1.0'; $self->{oid_sysName} = '.1.3.6.1.2.1.1.5.0'; $self->{oids} = [$self->{oid_sysDescr}, $self->{oid_sysName}]; $self->{extra_oids} = {}; if (defined($self->{option_results}->{extra_oids})) { my @extra_oids = split(/,/, $self->{option_results}->{extra_oids}); foreach my $extra_oid (@extra_oids) { next if ($extra_oid eq ''); my @values = split(/=/, $extra_oid); my ($name, $oid) = ('', $values[0]); if (defined($values[1])) { $name = $values[0]; $oid = $values[1]; } $oid =~ s/^(\d+)/\.$1/; $self->{extra_oids}->{$oid} = $name; push @{$self->{oids}}, $oid; } } } sub define_type { my ($self, %options) = @_; return 'unknown' unless (defined($options{desc}) && $options{desc} ne ''); foreach (@$discovery_match) { if ($options{desc} =~ /$_->{re}/) { return $_->{type}; } } return 'unknown'; } sub snmp_request { my ($self, %options) = @_; $self->{snmp}->set_snmp_connect_params(DestHost => $options{ip}); $self->{snmp}->set_snmp_connect_params(Community => $options{community}); $self->{snmp}->set_snmp_connect_params(Version => $options{version}); $self->{snmp}->set_snmp_connect_params(RemotePort => $options{port}); return undef if ($self->{snmp}->connect(dont_quit => 1) != 0); return $self->{snmp}->get_leef( oids => $self->{oids}, nothing_quit => 0, dont_quit => 1 ); } sub run { my ($self, %options) = @_; my @disco_data; my $disco_stats; my $last_version; my $last_community; my $subnet = NetAddr::IP->new($self->{option_results}->{subnet}); $disco_stats->{start_time} = time(); foreach my $ip (@{$subnet->splitref($subnet->bits())}) { my $result; foreach my $community (@{$self->{option_results}->{snmp_community}}) { foreach my $version (@{$self->{option_results}->{snmp_version}}) { $result = $self->snmp_request( ip => $ip->addr, community => $community, version => $version, port => $self->{option_results}->{snmp_port} ); $last_version = $version; $last_community = $community; last if (defined($result)); } } next if (!defined($result) || $result eq ''); my %host; $host{type} = $self->define_type(desc => $result->{$self->{oid_sysDescr}}); $host{desc} = $result->{$self->{oid_sysDescr}}; $host{desc} =~ s/\n/ /g if (defined($host{desc})); $host{ip} = $ip->addr; $host{hostname} = $result->{$self->{oid_sysName}}; $host{snmp_version} = $last_version; $host{snmp_community} = $last_community; $host{snmp_port} = $self->{option_results}->{snmp_port}; $host{extra_oids} = []; foreach (keys %{$self->{extra_oids}}) { my $label = defined($self->{extra_oids}->{$_}) && $self->{extra_oids}->{$_} ne '' ? $self->{extra_oids}->{$_} : $_; my $value = defined($result->{$_}) ? $result->{$_} : 'unknown'; push @{$host{extra_oids}}, { oid => $label, value => $value }; } push @disco_data, \%host; } $disco_stats->{end_time} = time(); $disco_stats->{duration} = $disco_stats->{end_time} - $disco_stats->{start_time}; $disco_stats->{discovered_items} = @disco_data; $disco_stats->{results} = \@disco_data; my $encoded_data; eval { if (defined($self->{option_results}->{prettify})) { $encoded_data = JSON::XS->new->utf8->pretty->encode($disco_stats); } else { $encoded_data = JSON::XS->new->utf8->encode($disco_stats); } }; if ($@) { $encoded_data = '{"code":"encode_error","message":"Cannot encode discovered data into JSON format"}'; } $self->{output}->output_add(short_msg => $encoded_data); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1); $self->{output}->exit(); } 1; =head1 MODE Resources discovery. =over 8 =item B<--subnet> Specify subnet from which discover resources (must be <ip>/<cidr> format) (mandatory). =item B<--snmp-port> Specify SNMP port (default: 161). =item B<--snmp-version> Specify SNMP version (can be defined multiple times) (mandatory). =item B<--snmp-community> Specify SNMP community (can be defined multiple times) (mandatory). =item B<--snmp-timeout> Specify SNMP timeout in second (default: 1). =item B<--prettify> Prettify JSON output. =item B<--extra-oids> Specify extra OIDs to get (example: --extra-oids='hrSystemInitialLoadParameters=1.3.6.1.2.1.25.1.4.0,sysDescr=.1.3.6.1.2.1.1.1.0'). =back =cut OS_LINUX_LOCAL_MODE_DISCOVERYSNMP $fatpacked{"os/linux/local/mode/discoverysnmpv3.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_DISCOVERYSNMPV3'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::discoverysnmpv3; use base qw(centreon::plugins::mode); use strict; use warnings; use os::linux::local::mode::resources::discovery qw($discovery_match); use centreon::plugins::snmp; use NetAddr::IP; use JSON::XS; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'subnet:s' => { name => 'subnet' }, 'authpassphrase:s' => { name => 'snmp_auth_passphrase' }, 'privpassphrase:s' => { name => 'snmp_priv_passphrase' }, 'authprotocol:s' => { name => 'snmp_auth_protocol' }, 'privprotocol:s' => { name => 'snmp_priv_protocol' }, 'snmp-username:s' => { name => 'snmp_security_name' }, 'snmp-port:s' => { name => 'snmp_port', default => 161 }, 'snmp-timeout:s' => { name => 'snmp_timeout', default => 1 }, 'prettify' => { name => 'prettify' }, 'extra-oids:s' => { name => 'extra_oids' } }); $self->{snmp} = centreon::plugins::snmp->new(%options, noptions => 1); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (!defined($self->{option_results}->{subnet}) || $self->{option_results}->{subnet} !~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/) { $self->{output}->add_option_msg(short_msg => "Need to specify --subnet option (<ip>/<cidr>)."); $self->{output}->option_exit(); } delete $self->{snmp}->{snmp_params}->{Community}; $self->{snmp}->set_snmp_connect_params(SecName => $self->{option_results}->{snmp_security_name}) if (defined($self->{option_results}->{snmp_security_name})); if (!defined($self->{option_results}->{snmp_security_name}) || $self->{option_results}->{snmp_security_name} eq '') { $self->{output}->add_option_msg(short_msg => 'Missing parameter Security Name.'); $self->{output}->option_exit(); } # unauthenticated and unencrypted $self->{snmp}->set_snmp_connect_params(SecLevel => 'noAuthNoPriv'); my $user_activate = 0; if (defined($self->{option_results}->{snmp_auth_passphrase}) && $self->{option_results}->{snmp_auth_passphrase} ne '') { if (!defined($self->{option_results}->{snmp_auth_protocol})) { $self->{output}->add_option_msg(short_msg => 'Missing parameter authenticate protocol.'); $self->{output}->option_exit(); } $self->{option_results}->{snmp_auth_protocol} = uc($self->{option_results}->{snmp_auth_protocol}); if ($self->{option_results}->{snmp_auth_protocol} !~ /^(?:MD5|SHA|SHA224|SHA256|SHA384|SHA512)$/) { $self->{output}->add_option_msg(short_msg => 'Wrong authentication protocol.'); $self->{output}->option_exit(); } $self->{snmp}->set_snmp_connect_params(SecLevel => 'authNoPriv'); $self->{snmp}->set_snmp_connect_params(AuthProto => $self->{option_results}->{snmp_auth_protocol}); $self->{snmp}->set_snmp_connect_params(AuthPass => $self->{option_results}->{snmp_auth_passphrase}); $user_activate = 1; } if (defined($self->{option_results}->{snmp_priv_passphrase}) && $self->{option_results}->{snmp_priv_passphrase} ne '') { if (!defined($self->{option_results}->{snmp_priv_protocol})) { $self->{output}->add_option_msg(short_msg => 'Missing parameter privacy protocol.'); $self->{output}->option_exit(); } $self->{option_results}->{snmp_priv_protocol} = uc($self->{option_results}->{snmp_priv_protocol}); if ($self->{option_results}->{snmp_priv_protocol} !~ /^(?:DES|AES|AES192|AES192C|AES256|AES256C)$/) { $self->{output}->add_option_msg(short_msg => 'Wrong privacy protocol.'); $self->{output}->option_exit(); } if ($user_activate == 0) { $self->{output}->add_option_msg(short_msg => 'Cannot use snmp v3 privacy option without snmp v3 authentification options.'); $self->{output}->option_exit(); } $self->{snmp}->set_snmp_connect_params(SecLevel => 'authPriv'); $self->{snmp}->set_snmp_connect_params(PrivPass => $self->{option_results}->{snmp_priv_passphrase}); $self->{snmp}->set_snmp_connect_params(PrivProto => $self->{option_results}->{snmp_priv_protocol}); } $self->{snmpv3_combo} = ''; my $options_mapping = { snmp_auth_passphrase => "--authpassphrase", snmp_priv_passphrase => "--privpassphrase" , snmp_auth_protocol => "--authprotocol", snmp_priv_protocol => "--privprotocol", snmp_security_name => "--snmp-username" }; foreach my $option (keys %$options_mapping) { next if (!defined($self->{option_results}->{$option}) || $self->{option_results}->{$option} eq ""); $self->{snmpv3_combo} .= " " . $options_mapping->{$option} . "='" . $self->{option_results}->{$option} . "'"; } $self->{snmp}->set_snmp_connect_params(Timeout => $self->{option_results}->{snmp_timeout} * (10**6)); $self->{snmp}->set_snmp_connect_params(Retries => 0); $self->{snmp}->set_snmp_connect_params(RemotePort => $self->{option_results}->{port}); $self->{snmp}->set_snmp_connect_params(Version => 3); $self->{snmp}->set_snmp_params(subsetleef => 1); $self->{snmp}->set_snmp_params(snmp_autoreduce => 0); $self->{snmp}->set_snmp_params(snmp_errors_exit => 'unknown'); $self->{oid_sysDescr} = '.1.3.6.1.2.1.1.1.0'; $self->{oid_sysName} = '.1.3.6.1.2.1.1.5.0'; $self->{oids} = [$self->{oid_sysDescr}, $self->{oid_sysName}]; $self->{extra_oids} = {}; if (defined($self->{option_results}->{extra_oids})) { my @extra_oids = split(/,/, $self->{option_results}->{extra_oids}); foreach my $extra_oid (@extra_oids) { next if ($extra_oid eq ''); my @values = split(/=/, $extra_oid); my ($name, $oid) = ('', $values[0]); if (defined($values[1])) { $name = $values[0]; $oid = $values[1]; } $oid =~ s/^(\d+)/\.$1/; $self->{extra_oids}->{$oid} = $name; push @{$self->{oids}}, $oid; } } } sub define_type { my ($self, %options) = @_; return 'unknown' unless (defined($options{desc}) && $options{desc} ne ''); foreach (@$discovery_match) { if ($options{desc} =~ /$_->{re}/) { return $_->{type}; } } return 'unknown'; } sub snmp_request { my ($self, %options) = @_; $self->{snmp}->set_snmp_connect_params(DestHost => $options{ip}); return undef if ($self->{snmp}->connect(dont_quit => 1) != 0); return $self->{snmp}->get_leef( oids => $self->{oids}, nothing_quit => 0, dont_quit => 1 ); } sub run { my ($self, %options) = @_; my @disco_data; my $disco_stats; my $subnet = NetAddr::IP->new($self->{option_results}->{subnet}); $disco_stats->{start_time} = time(); foreach my $ip (@{$subnet->splitref($subnet->bits())}) { my $snmp_result = $self->snmp_request(ip => $ip->addr); next if (!defined($snmp_result)); next if (!defined($snmp_result->{$self->{oid_sysDescr}}) && !defined($snmp_result->{$self->{oid_sysName}})); my %host; $host{type} = $self->define_type(desc => $snmp_result->{$self->{oid_sysDescr}}); $host{desc} = $snmp_result->{$self->{oid_sysDescr}}; $host{desc} =~ s/\n/ /g if (defined($host{desc})); $host{ip} = $ip->addr; $host{hostname} = $snmp_result->{$self->{oid_sysName}}; $host{snmp_version} = '3'; $host{snmp_port} = $self->{option_results}->{snmp_port}; $host{snmpv3_extraopts} = $self->{snmpv3_combo}; $host{extra_oids} = []; foreach (keys %{$self->{extra_oids}}) { my $label = defined($self->{extra_oids}->{$_}) && $self->{extra_oids}->{$_} ne '' ? $self->{extra_oids}->{$_} : $_; my $value = defined($snmp_result->{$_}) ? $snmp_result->{$_} : 'unknown'; push @{$host{extra_oids}}, { oid => $label, value => $value }; } push @disco_data, \%host; } $disco_stats->{end_time} = time(); $disco_stats->{duration} = $disco_stats->{end_time} - $disco_stats->{start_time}; $disco_stats->{discovered_items} = @disco_data; $disco_stats->{results} = \@disco_data; my $encoded_data; eval { if (defined($self->{option_results}->{prettify})) { $encoded_data = JSON::XS->new->utf8->pretty->encode($disco_stats); } else { $encoded_data = JSON::XS->new->utf8->encode($disco_stats); } }; if ($@) { $encoded_data = '{"code":"encode_error","message":"Cannot encode discovered data into JSON format"}'; } $self->{output}->output_add(short_msg => $encoded_data); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1); $self->{output}->exit(); } 1; =head1 MODE Resources discovery. =over 8 =item B<--subnet> Specify subnet from which discover resources (must be <ip>/<cidr> format) (mandatory). Specify SNMP community (can be defined multiple times) (mandatory). =item B<--snmp-timeout> Specify SNMP timeout in second (default: 1). =item B<--prettify> Prettify JSON output. =item B<--extra-oids> Specify extra OIDs to get (example: --extra-oids='hrSystemInitialLoadParameters=1.3.6.1.2.1.25.1.4.0,sysDescr=.1.3.6.1.2.1.1.1.0'). =back =cut OS_LINUX_LOCAL_MODE_DISCOVERYSNMPV3 $fatpacked{"os/linux/local/mode/diskio.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_DISKIO'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::diskio; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use Digest::MD5 qw(md5_hex); sub custom_usage_calc { my ($self, %options) = @_; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; $self->{result_values}->{usage_persecond} = ($options{new_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_sectors'} - $options{old_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_sectors'}) * $self->{instance_mode}->{option_results}->{bytes_per_sector} / $options{delta_time}; return 0; } sub custom_wait_calc { my ($self, %options) = @_; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; $self->{result_values}->{wait} = 0; my $tput = ($options{new_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_ios'} - $options{old_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_ios'}); if ($tput) { $self->{result_values}->{wait} = ($options{new_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_ticks'} - $options{old_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref} . '_ticks'}) / $tput; } return 0; } sub custom_utils_calc { my ($self, %options) = @_; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; my $delta_ms = $self->{instance_mode}->{option_results}->{interrupt_frequency} * ( ($options{new_datas}->{$self->{instance} . '_cpu_idle'} - $options{old_datas}->{$self->{instance} . '_cpu_idle'}) + ($options{new_datas}->{$self->{instance} . '_cpu_user'} - $options{old_datas}->{$self->{instance} . '_cpu_user'}) + ($options{new_datas}->{$self->{instance} . '_cpu_iowait'} - $options{old_datas}->{$self->{instance} . '_cpu_iowait'}) + ($options{new_datas}->{$self->{instance} . '_cpu_system'} - $options{old_datas}->{$self->{instance} . '_cpu_system'}) + ($options{new_datas}->{$self->{instance} . '_cpu_hardirq'} - $options{old_datas}->{$self->{instance} . '_cpu_hardirq'}) + ($options{new_datas}->{$self->{instance} . '_cpu_softirq'} - $options{old_datas}->{$self->{instance} . '_cpu_softirq'}) + ($options{new_datas}->{$self->{instance} . '_cpu_nice'} - $options{old_datas}->{$self->{instance} . '_cpu_nice'}) + ($options{new_datas}->{$self->{instance} . '_cpu_steal'} - $options{old_datas}->{$self->{instance} . '_cpu_steal'}) ) / $options{new_datas}->{$self->{instance} . '_cpu_total'} / 100; $self->{result_values}->{utils} = 0; if ($delta_ms != 0) { $self->{result_values}->{utils} = 100 * ($options{new_datas}->{$self->{instance} . '_ticks'} - $options{old_datas}->{$self->{instance} . '_ticks'}) / $delta_ms; $self->{result_values}->{utils} = 100 if ($self->{result_values}->{utils} > 100); } return 0; } sub custom_svctm_calc { my ($self, %options) = @_; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; my $nr_ios = ($options{new_datas}->{$self->{instance} . '_rd_ios'} - $options{old_datas}->{$self->{instance} . '_rd_ios'}) + ($options{new_datas}->{$self->{instance} . '_wr_ios'} - $options{old_datas}->{$self->{instance} . '_wr_ios'}); my $itv = ( ($options{new_datas}->{$self->{instance} . '_cpu_idle'} - $options{old_datas}->{$self->{instance} . '_cpu_idle'}) + ($options{new_datas}->{$self->{instance} . '_cpu_user'} - $options{old_datas}->{$self->{instance} . '_cpu_user'}) + ($options{new_datas}->{$self->{instance} . '_cpu_iowait'} - $options{old_datas}->{$self->{instance} . '_cpu_iowait'}) + ($options{new_datas}->{$self->{instance} . '_cpu_system'} - $options{old_datas}->{$self->{instance} . '_cpu_system'}) + ($options{new_datas}->{$self->{instance} . '_cpu_hardirq'} - $options{old_datas}->{$self->{instance} . '_cpu_hardirq'}) + ($options{new_datas}->{$self->{instance} . '_cpu_softirq'} - $options{old_datas}->{$self->{instance} . '_cpu_softirq'}) + ($options{new_datas}->{$self->{instance} . '_cpu_nice'} - $options{old_datas}->{$self->{instance} . '_cpu_nice'}) + ($options{new_datas}->{$self->{instance} . '_cpu_steal'} - $options{old_datas}->{$self->{instance} . '_cpu_steal'}) ) / $options{new_datas}->{$self->{instance} . '_cpu_total'} * 100; $self->{result_values}->{svctm} = 0; if ($itv > 0) { my $tput = $nr_ios * $self->{instance_mode}->{option_results}->{interrupt_frequency} / $itv; my $util = ($options{new_datas}->{$self->{instance} . '_ticks'} - $options{old_datas}->{$self->{instance} . '_ticks'}) / $itv * $self->{instance_mode}->{option_results}->{interrupt_frequency}; $self->{result_values}->{svctm} = $tput > 0 ? $util / $tput : 0; } return 0; } sub prefix_device_output { my ($self, %options) = @_; return "Device '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'device', type => 1, cb_prefix_output => 'prefix_device_output', message_multiple => 'All devices are ok', skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{device} = [ { label => 'read-usage', nlabel => 'device.io.read.usage.bytespersecond', set => { key_values => [ { name => 'read_sectors', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'read' }, output_template => 'read I/O : %s %s/s', output_change_bytes => 1, output_use => 'usage_persecond', threshold_use => 'usage_persecond', perfdatas => [ { label => 'readio', value => 'usage_persecond', template => '%d', unit => 'B/s', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'write-usage', nlabel => 'device.io.write.usage.bytespersecond', set => { key_values => [ { name => 'write_sectors', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'write' }, output_template => 'write I/O : %s %s/s', output_change_bytes => 1, output_use => 'usage_persecond', threshold_use => 'usage_persecond', perfdatas => [ { label => 'writeio', value => 'usage_persecond', template => '%d', unit => 'B/s', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'read-wait', nlabel => 'device.io.read.wait.milliseconds', set => { key_values => [ { name => 'rd_ios', diff => 1 }, { name => 'rd_ticks', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_wait_calc'), closure_custom_calc_extra_options => { label_ref => 'rd' }, output_template => 'read wait: %.2f ms', output_change_bytes => 1, output_use => 'wait', threshold_use => 'wait', perfdatas => [ { label => 'readwait', value => 'wait', template => '%.2f', unit => 'ms', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'write-wait', nlabel => 'device.io.write.wait.milliseconds', set => { key_values => [ { name => 'wr_ios', diff => 1 }, { name => 'wr_ticks', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_wait_calc'), closure_custom_calc_extra_options => { label_ref => 'wr' }, output_template => 'write wait: %.2f ms', output_change_bytes => 1, output_use => 'wait', threshold_use => 'wait', perfdatas => [ { label => 'writewait', value => 'wait', template => '%.2f', unit => 'ms', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'svctime', nlabel => 'device.io.servicetime.count', set => { key_values => [ { name => 'cpu_total', diff => 1 }, { name => 'cpu_iowait', diff => 1 }, { name => 'cpu_user', diff => 1 }, { name => 'cpu_system', diff => 1 }, { name => 'cpu_idle', diff => 1 }, { name => 'cpu_hardirq', diff => 1 }, { name => 'cpu_softirq', diff => 1 }, { name => 'cpu_steal', diff => 1 }, { name => 'cpu_nice', diff => 1 }, { name => 'ticks', diff => 1 }, { name => 'rd_ios', diff => 1 }, { name => 'wr_ios', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_svctm_calc'), output_template => 'svctm: %.2f', output_use => 'svctm', threshold_use => 'svctm', perfdatas => [ { label => 'svctm', value => 'svctm', template => '%.2f', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'utils', nlabel => 'device.io.utils.percentage', set => { key_values => [ { name => 'cpu_total', diff => 1 }, { name => 'cpu_iowait', diff => 1 }, { name => 'cpu_user', diff => 1 }, { name => 'cpu_system', diff => 1 }, { name => 'cpu_idle', diff => 1 }, { name => 'cpu_hardirq', diff => 1 }, { name => 'cpu_softirq', diff => 1 }, { name => 'cpu_steal', diff => 1 }, { name => 'cpu_nice', diff => 1 }, { name => 'ticks', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_utils_calc'), output_template => '%%utils: %.2f %%', output_use => 'utils', threshold_use => 'utils', perfdatas => [ { label => 'utils', value => 'utils', template => '%.2f', unit => '%', min => 0, max => 100, label_extra_instance => 1, instance_use => 'display' } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-partition-name:s' => { name => 'filter_partition_name' }, 'exclude-partition-name:s' => { name => 'exclude_partition_name' }, 'interrupt-frequency:s' => { name => 'interrupt_frequency', default => 1000 }, 'bytes-per-sector:s' => { name => 'bytes_per_sector', default => 512 }, 'skip' => { name => 'skip' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'tail', command_options => '-n +1 /proc/stat /proc/diskstats 2>&1' ); $stdout =~ /\/proc\/stat(.*?)\/proc\/diskstats.*?\n(.*)/msg; my ($cpu_parts, $disk_parts) = ($1, $2); # Manage CPU Parts $cpu_parts =~ /^cpu\s+(.*)$/ms; my @stats = split(/\s+/, $1); my ($cpu_idle, $cpu_nice, $cpu_system, $cpu_user) = ($stats[3], $stats[1], $stats[2], $stats[0]); my ($cpu_iowait, $cpu_hardirq, $cpu_softirq, $cpu_steal) = (0, 0, 0, 0); $cpu_iowait = $stats[4] if (defined($stats[4])); $cpu_hardirq = $stats[5] if (defined($stats[5])); $cpu_softirq = $stats[6] if (defined($stats[6])); $cpu_steal = $stats[7] if (defined($stats[7])); my $cpu_total = 0; while ($cpu_parts =~ /^cpu(\d+)/msg) { $cpu_total++; } $self->{device} = {}; while ($disk_parts =~ /^\s*\S+\s+\S+\s+(\S+)\s+(\d+)\s+\d+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+\s+(\d+)\s+(\d+)\s+\d+\s+(\d+)\s+/mg) { my ($partition_name, $read_sector, $write_sector, $rd_ios, $rd_ticks, $wr_ios, $wr_ticks, $ms_ticks) = ($1, $3, $6, $2, $4, $5, $7, $8); next if (defined($self->{option_results}->{filter_partition_name}) && $self->{option_results}->{filter_partition_name} ne '' && $partition_name !~ /$self->{option_results}->{filter_partition_name}/); next if (defined($self->{option_results}->{exclude_partition_name}) && $self->{option_results}->{exclude_partition_name} ne '' && $partition_name =~ /$self->{option_results}->{exclude_partition_name}/); if (defined($self->{option_results}->{skip}) && $read_sector == 0 && $write_sector == 0) { $self->{output}->output_add(long_msg => "skipping device '" . $partition_name . "': no read/write IO.", debug => 1); next; } $self->{device}->{$partition_name} = { display => $partition_name, read_sectors => $read_sector, write_sectors => $write_sector, rd_ios => $rd_ios, rd_ticks => $rd_ticks, wr_ios => $wr_ios, wr_ticks => $wr_ticks, ticks => $ms_ticks, cpu_total => $cpu_total, cpu_system => $cpu_system, cpu_idle => $cpu_idle, cpu_user => $cpu_user, cpu_iowait => $cpu_iowait, cpu_hardirq => $cpu_hardirq, cpu_softirq => $cpu_softirq, cpu_steal => $cpu_steal, cpu_nice => $cpu_nice }; } if (scalar(keys %{$self->{device}}) <= 0) { if (defined($self->{option_results}->{name})) { $self->{output}->add_option_msg(short_msg => "No device found for name '" . $self->{option_results}->{name} . "'."); } else { $self->{output}->add_option_msg(short_msg => "No device found."); } $self->{output}->option_exit(); } $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_partition_name}) ? md5_hex($self->{option_results}->{filter_partition_name}) : md5_hex('all')); } 1; =head1 MODE Check some disk io counters: read and writes bytes per seconds, milliseconds time spent reading and writing, %util (like iostat) Command used: tail -n +1 /proc/stat /proc/diskstats 2>&1 =over 8 =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'read-usage', 'write-usage', 'read-wait', 'write-wait', 'svctime', 'utils'. =item B<--filter-partition-name> Filter partition name (regexp can be used). =item B<--exclude-partition-name> Exclude partition name (regexp can be used). =item B<--bytes-per-sector> Bytes per sector (default: 512) =item B<--interrupt-frequency> Linux Kernel Timer Interrupt Frequency (default: 1000) =item B<--skip> Skip partitions with 0 sectors read/write. =back =cut OS_LINUX_LOCAL_MODE_DISKIO $fatpacked{"os/linux/local/mode/filesdate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_FILESDATE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::filesdate; use base qw(centreon::plugins::mode); use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'warning:s' => { name => 'warning' }, 'critical:s' => { name => 'critical' }, 'separate-dirs' => { name => 'separate_dirs' }, 'max-depth:s' => { name => 'max_depth' }, 'exclude-du:s@' => { name => 'exclude_du' }, 'filter-plugin:s' => { name => 'filter_plugin' }, 'files:s' => { name => 'files' }, 'time:s' => { name => 'time' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (($self->{perfdata}->threshold_validate(label => 'warning', value => $self->{option_results}->{warning})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $self->{option_results}->{warning} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical', value => $self->{option_results}->{critical})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $self->{option_results}->{critical} . "'."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{files}) || $self->{option_results}->{files} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify files option."); $self->{output}->option_exit(); } #### Create command_options $self->{command_options} = '-x --time-style=+%s'; if (defined($self->{option_results}->{separate_dirs})) { $self->{command_options} .= ' --separate-dirs'; } if (defined($self->{option_results}->{max_depth})) { $self->{command_options} .= ' --max-depth=' . $self->{option_results}->{max_depth}; } if (defined($self->{option_results}->{time})) { $self->{command_options} .= ' --time=' . $self->{option_results}->{time}; } else { $self->{command_options} .= ' --time'; } foreach my $exclude (@{$self->{option_results}->{exclude_du}}) { $self->{command_options} .= " --exclude='" . $exclude . "'"; } $self->{command_options} .= ' ' . $self->{option_results}->{files}; $self->{command_options} .= ' 2>&1'; } sub run { my ($self, %options) = @_; my $total_size = 0; my $current_time = time(); my ($stdout) = $options{custom}->execute_command( command => 'du', command_options => $self->{command_options} ); $self->{output}->output_add( severity => 'OK', short_msg => 'All file/directory times are ok.' ); foreach (split(/\n/, $stdout)) { next if (!/(\d+)\t+(\d+)\t+(.*)/); my ($size, $time, $name) = ($1, $2, centreon::plugins::misc::trim($3)); my $diff_time = $current_time - $time; next if (defined($self->{option_results}->{filter_plugin}) && $self->{option_results}->{filter_plugin} ne '' && $name !~ /$self->{option_results}->{filter_plugin}/); my $exit_code = $self->{perfdata}->threshold_check( value => $diff_time, threshold => [ { label => 'critical', 'exit_litteral' => 'critical' }, { label => 'warning', exit_litteral => 'warning' } ] ); $self->{output}->output_add(long_msg => sprintf("%s: %s seconds (time: %s)", $name, $diff_time, scalar(localtime($time)))); if (!$self->{output}->is_status(litteral => 1, value => $exit_code, compare => 'ok')) { $self->{output}->output_add( severity => $exit_code, short_msg => sprintf('%s: %s seconds (time: %s)', $name, $diff_time, scalar(localtime($time))) ); } $self->{output}->perfdata_add( nlabel => 'file.mtime.last.seconds', instances => $name, unit => 's', value => $diff_time, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical') ); } $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check time (modified, creation,...) of files/directories. =over 8 =item B<--files> Files/Directories to check. (Shell expansion is ok) =item B<--warning> Warning threshold in seconds for each files/directories (diff time). =item B<--critical> Critical threshold in seconds for each files/directories (diff time). =item B<--separate-dirs> Do not include size of subdirectories. =item B<--max-depth> Don't check fewer levels. (can be use --separate-dirs) =item B<--time> Check another time than modified time. =item B<--exclude-du> Exclude files/directories with 'du' command. Values from exclude files/directories are not counted in parent directories. Shell pattern can be used. =item B<--filter-plugin> Filter files/directories in the plugin. Values from exclude files/directories are counted in parent directories!!! Perl Regexp can be used. =back =cut OS_LINUX_LOCAL_MODE_FILESDATE $fatpacked{"os/linux/local/mode/filessize.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_FILESSIZE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::filessize; use base qw(centreon::plugins::mode); use strict; use warnings; use centreon::plugins::misc; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'warning-one:s' => { name => 'warning_one' }, 'critical-one:s' => { name => 'critical_one' }, 'warning-total:s' => { name => 'warning_total' }, 'critical-total:s' => { name => 'critical_total' }, 'separate-dirs' => { name => 'separate_dirs' }, 'max-depth:s' => { name => 'max_depth' }, 'all-files' => { name => 'all_files' }, 'exclude-du:s@' => { name => 'exclude_du' }, 'filter-plugin:s' => { name => 'filter_plugin' }, 'files:s' => { name => 'files' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (($self->{perfdata}->threshold_validate(label => 'warning_one', value => $self->{option_results}->{warning_one})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning-one threshold '" . $self->{warning_one} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical_one', value => $self->{option_results}->{critical_one})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical-one threshold '" . $self->{critical_one} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'warning_total', value => $self->{option_results}->{warning_total})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning-total threshold '" . $self->{warning_total} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical_total', value => $self->{option_results}->{critical_total})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical-total threshold '" . $self->{critical_total} . "'."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{files}) || $self->{option_results}->{files} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify files option."); $self->{output}->option_exit(); } $self->{command_options} = '-x -b'; if (defined($self->{option_results}->{separate_dirs})) { $self->{command_options} .= ' --separate-dirs'; } if (defined($self->{option_results}->{max_depth})) { $self->{command_options} .= ' --max-depth=' . $self->{option_results}->{max_depth}; } if (defined($self->{option_results}->{all_files})) { $self->{command_options} .= ' --all'; } foreach my $exclude (@{$self->{option_results}->{exclude_du}}) { $self->{command_options} .= " --exclude='" . $exclude . "'"; } $self->{command_options} .= ' ' . $self->{option_results}->{files}; $self->{command_options} .= ' 2>&1'; } sub run { my ($self, %options) = @_; my $total_size = 0; my ($stdout) = $options{custom}->execute_command( command => 'du', command_options => $self->{command_options} ); $self->{output}->output_add( severity => 'OK', short_msg => "All file/directory sizes are ok." ); foreach (split(/\n/, $stdout)) { next if (!/(\d+)\t+(.*)/); my ($size, $name) = ($1, centreon::plugins::misc::trim($2)); next if (defined($self->{option_results}->{filter_plugin}) && $self->{option_results}->{filter_plugin} ne '' && $name !~ /$self->{option_results}->{filter_plugin}/); $total_size += $size; my $exit_code = $self->{perfdata}->threshold_check( value => $size, threshold => [ { label => 'critical_one', exit_litteral => 'critical' }, { label => 'warning_one', exit_litteral => 'warning' } ] ); my ($size_value, $size_unit) = $self->{perfdata}->change_bytes(value => $size); $self->{output}->output_add(long_msg => sprintf("%s: %s", $name, $size_value . ' ' . $size_unit)); if (!$self->{output}->is_status(litteral => 1, value => $exit_code, compare => 'ok')) { $self->{output}->output_add( severity => $exit_code, short_msg => sprintf("'%s' size is %s", $name, $size_value . ' ' . $size_unit) ); } $self->{output}->perfdata_add( nlabel => 'file.size.bytes', instances => $name, unit => 'B', value => $size, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning_one'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical_one'), min => 0 ); } # Total Size my $exit_code = $self->{perfdata}->threshold_check( value => $total_size, threshold => [ { label => 'critical_total', exit_litteral => 'critical' }, { label => 'warning_total', exit_litteral => 'warning' } ] ); my ($size_value, $size_unit) = $self->{perfdata}->change_bytes(value => $total_size); $self->{output}->output_add(long_msg => sprintf("Total: %s", $size_value . ' ' . $size_unit)); if (!$self->{output}->is_status(litteral => 1, value => $exit_code, compare => 'ok')) { $self->{output}->output_add( severity => $exit_code, short_msg => sprintf('Total size is %s', $size_value . ' ' . $size_unit) ); } $self->{output}->perfdata_add( nlabel => 'files.size.bytes', unit => 'B', value => $total_size, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning_total'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical_total'), min => 0 ); $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check size of files/directories. =over 8 =item B<--files> Files/Directories to check. (Shell expansion is ok) =item B<--warning-one> Warning threshold in bytes for each files/directories. =item B<--critical-one> Critical threshold in bytes for each files/directories. =item B<--warning-total> Warning threshold in bytes for all files/directories. =item B<--critical-total> Critical threshold in bytes for all files/directories. =item B<--separate-dirs> Do not include size of subdirectories. =item B<--max-depth> Don't check fewer levels. (can be use --separate-dirs) =item B<--all-files> Add files when you check directories. =item B<--exclude-du> Exclude files/directories with 'du' command. Values from exclude files/directories are not counted in parent directories. Shell pattern can be used. =item B<--filter-plugin> Filter files/directories in the plugin. Values from exclude files/directories are counted in parent directories!!! Perl Regexp can be used. =back =cut OS_LINUX_LOCAL_MODE_FILESSIZE $fatpacked{"os/linux/local/mode/inodes.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_INODES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::inodes; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub prefix_inodes_output { my ($self, %options) = @_; return "Inodes partition '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'inodes', type => 1, cb_prefix_output => 'prefix_inodes_output', message_multiple => 'All inode partitions are ok' } ]; $self->{maps_counters}->{inodes} = [ { label => 'usage', nlabel => 'storage.inodes.usage.percentage', set => { key_values => [ { name => 'used' }, { name => 'display' } ], output_template => 'used: %s %%', perfdatas => [ { label => 'used', template => '%d', unit => '%', min => 0, max => 100, label_extra_instance => 1 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-type:s' => { name => 'filter_type' }, 'filter-fs:s' => { name => 'filter_fs' }, 'exclude-fs:s' => { name => 'exclude_fs' }, 'filter-mountpoint:s' => { name => 'filter_mountpoint' }, 'exclude-mountpoint:s' => { name => 'exclude_mountpoint' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout, $exit_code) = $options{custom}->execute_command( command => 'df', command_options => '-P -i -T 2>&1', no_quit => 1 ); $self->{inodes} = {}; my @lines = split /\n/, $stdout; foreach my $line (@lines) { next if ($line !~ /^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/); my ($fs, $type, $size, $used, $available, $percent, $mount) = ($1, $2, $3, $4, $5, $6, $7); next if (defined($self->{option_results}->{filter_fs}) && $self->{option_results}->{filter_fs} ne '' && $fs !~ /$self->{option_results}->{filter_fs}/); next if (defined($self->{option_results}->{exclude_fs}) && $self->{option_results}->{exclude_fs} ne '' && $fs =~ /$self->{option_results}->{exclude_fs}/); next if (defined($self->{option_results}->{filter_type}) && $self->{option_results}->{filter_type} ne '' && $type !~ /$self->{option_results}->{filter_type}/); next if (defined($self->{option_results}->{filter_mountpoint}) && $self->{option_results}->{filter_mountpoint} ne '' && $mount !~ /$self->{option_results}->{filter_mountpoint}/); next if (defined($self->{option_results}->{exclude_mountpoint}) && $self->{option_results}->{exclude_mountpoint} ne '' && $mount =~ /$self->{option_results}->{exclude_mountpoint}/); $percent =~ s/%//g; next if ($percent eq '-'); $self->{inodes}->{$mount} = { display => $mount, fs => $fs, type => $type, total => $size, used => $percent }; } if (scalar(keys %{$self->{inodes}}) <= 0) { if ($exit_code != 0) { $self->{output}->output_add(long_msg => "command output:" . $stdout); } $self->{output}->add_option_msg(short_msg => "No storage found (filters or command issue)"); $self->{output}->option_exit(); } } 1; =head1 MODE Check Inodes space usage on partitions. Command used: df -P -i -T 2>&1 =over 8 =item B<--warning-usage> Warning threshold in percent. =item B<--critical-usage> Critical threshold in percent. =item B<--filter-mountpoint> Filter filesystem mount point (regexp can be used). =item B<--exclude-mountpoint> Exclude filesystem mount point (regexp can be used). =item B<--filter-type> Filter filesystem type (regexp can be used). =item B<--filter-fs> Filter filesystem (regexp can be used). =item B<--exclude-fs> Exclude filesystem (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_INODES $fatpacked{"os/linux/local/mode/listinterfaces.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LISTINTERFACES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::listinterfaces; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-name:s' => { name => 'filter_name' }, 'filter-state:s' => { name => 'filter_state' }, 'no-loopback' => { name => 'no_loopback' }, 'skip-novalues' => { name => 'skip_novalues' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command_path => '/sbin', command => 'ip', command_options => '-s addr 2>&1' ); my $mapping = { ifconfig => { get_interface => '^(\S+)(.*?)(\n\n|\n$)', test => 'RX bytes:\S+.*?TX bytes:\S+' }, iproute => { get_interface => '^\d+:\s+(\S+)(.*?)(?=\n\d|\Z$)', test => 'RX:\s+bytes.*?\d+' } }; my $type = 'ifconfig'; if ($stdout =~ /^\d+:\s+\S+:\s+</ms) { $type = 'iproute'; } my $results = {}; while ($stdout =~ /$mapping->{$type}->{get_interface}/msg) { my ($interface_name, $values) = ($1, $2); $interface_name =~ s/:$//; my $states = ''; $states .= 'R' if ($values =~ /RUNNING|LOWER_UP/ms); $states .= 'U' if ($values =~ /UP/ms); if (defined($self->{option_results}->{no_loopback}) && $values =~ /LOOPBACK/ms) { $self->{output}->output_add(long_msg => "Skipping interface '" . $interface_name . "': option --no-loopback"); next; } if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $interface_name !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "Skipping interface '" . $interface_name . "': no matching filter name"); next; } if (defined($self->{option_results}->{filter_state}) && $self->{option_results}->{filter_state} ne '' && $states !~ /$self->{option_results}->{filter_state}/) { $self->{output}->output_add(long_msg => "Skipping interface '" . $interface_name . "': no matching filter state"); next; } if (defined($self->{option_results}->{skip_novalues}) && $values =~ /$mapping->{$type}->{test}/msi) { $self->{output}->output_add(long_msg => "Skipping interface '" . $interface_name . "': no values"); next; } $results->{$interface_name} = { state => $states }; } return $results; } sub run { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->output_add(long_msg => "'" . $name . "' [state = '" . $results->{$name}->{state} . "']"); } $self->{output}->output_add( severity => 'OK', short_msg => 'List interfaces:' ); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); $self->{output}->exit(); } sub disco_format { my ($self, %options) = @_; $self->{output}->add_disco_format(elements => ['name', 'state']); } sub disco_show { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->add_disco_entry( name => $name, state => $results->{$name}->{state} ); } } 1; =head1 MODE List storages. Command used: /sbin/ip -s addr 2>&1 =over 8 =item B<--filter-name> Filter interface name (regexp can be used). =item B<--filter-state> Filter state (regexp can be used). Can be: 'R' (running), 'U' (up). =item B<--no-loopback> Don't display loopback interfaces. =item B<--skip-novalues> Filter interface without in/out byte values. =back =cut OS_LINUX_LOCAL_MODE_LISTINTERFACES $fatpacked{"os/linux/local/mode/listpartitions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LISTPARTITIONS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::listpartitions; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-name:s' => { name => 'filter_name' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/partitions 2>&1' ); my $results = {}; my @lines = split /\n/, $stdout; # Header not needed shift @lines; foreach my $line (@lines) { next if ($line !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/); my ($major, $minor, $blocks, $name) = ($1, $2, $3, $4); if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $name !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "Skipping partition '" . $name . "': no matching filter name"); next; } $results->{$name} = 1; } return $results; } sub run { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->output_add(long_msg => "'" . $name . "'"); } $self->{output}->output_add( severity => 'OK', short_msg => 'List partitions:' ); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); $self->{output}->exit(); } sub disco_format { my ($self, %options) = @_; $self->{output}->add_disco_format(elements => ['name']); } sub disco_show { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->add_disco_entry(name => $name); } } 1; =head1 MODE List partitions. Command used: cat /proc/partitions 2>&1 =over 8 =item B<--filter-name> Filter partition name (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_LISTPARTITIONS $fatpacked{"os/linux/local/mode/liststorages.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LISTSTORAGES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::liststorages; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-type:s' => { name => 'filter_type' }, 'filter-fs:s' => { name => 'filter_fs' }, 'filter-mount:s' => { name => 'filter_mount' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'df', command_options => '-P -k -T 2>&1', no_quit => 1 ); my $results = {}; my @lines = split /\n/, $stdout; foreach my $line (@lines) { next if ($line !~ /^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/); my ($fs, $type, $size, $used, $available, $percent, $mount) = ($1, $2, $3, $4, $5, $6, $7); if (defined($self->{option_results}->{filter_fs}) && $self->{option_results}->{filter_fs} ne '' && $fs !~ /$self->{option_results}->{filter_fs}/) { $self->{output}->output_add(long_msg => "skipping storage '" . $mount . "': no matching filter filesystem", debug => 1); next; } if (defined($self->{option_results}->{filter_type}) && $self->{option_results}->{filter_type} ne '' && $type !~ /$self->{option_results}->{filter_type}/) { $self->{output}->output_add(long_msg => "skipping storage '" . $mount . "': no matching filter filesystem type", debug => 1); next; } if (defined($self->{option_results}->{filter_mount}) && $self->{option_results}->{filter_mount} ne '' && $mount !~ /$self->{option_results}->{filter_mount}/) { $self->{output}->output_add(long_msg => "skipping storage '" . $mount . "': no matching filter mount point", debug => 1); next; } $results->{$mount} = { fs => $fs, type => $type, size => $size }; } return $results; } sub run { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->output_add(long_msg => "'" . $name . "' [fs = " . $results->{$name}->{fs} . '] [type = ' . $results->{$name}->{type} . '] [size = ' . $results->{$name}->{size} . ']'); } $self->{output}->output_add( severity => 'OK', short_msg => 'List storages:' ); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); $self->{output}->exit(); } sub disco_format { my ($self, %options) = @_; $self->{output}->add_disco_format(elements => ['name', 'fs', 'type', 'size']); } sub disco_show { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->add_disco_entry( name => $name, fs => $results->{$name}->{fs}, type => $results->{$name}->{type}, size => $results->{$name}->{size}, ); } } 1; =head1 MODE List storages. Command used: df -P -k -T 2>&1 =over 8 =item B<--filter-type> Filter filesystem type (regexp can be used). =item B<--filter-fs> Filter filesystem (regexp can be used). =item B<--filter-mount> Filter mount point (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_LISTSTORAGES $fatpacked{"os/linux/local/mode/listsystemdservices.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LISTSYSTEMDSERVICES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::listsystemdservices; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-name:s' => { name => 'filter_name' }, 'filter-description:s' => { name => 'filter_description' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); } sub manage_selection { my ($self, %options) = @_; # check systemctl version to convert no-legend in legend=false (change in versions >= 248) my $legend_format= ' --no-legend'; my ($stdout_version) = $options{custom}->execute_command( command => 'systemctl', command_options => '--version' ); $stdout_version =~ /^systemd\s(\d+)\s/; my $systemctl_version=$1; if($systemctl_version >= 248){ $legend_format = ' --legend=false'; } my $command_options_1 = '-a --no-pager --plain'; my ($stdout) = $options{custom}->execute_command( command => 'systemctl', command_options => $command_options_1.$legend_format ); my $results = {}; #auditd.service loaded active running Security Auditing Service #avahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stack #brandbot.service loaded inactive dead Flexible Branding Service while ($stdout =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)/mig) { my ($name, $load, $active, $sub, $desc) = ($1, $2, $3, $4, $5); $desc =~ s/\s+$//; next if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $name !~ /$self->{option_results}->{filter_name}/); next if (defined($self->{option_results}->{filter_description}) && $self->{option_results}->{filter_description} ne '' && $desc !~ /$self->{option_results}->{filter_description}/); $results->{$name} = { load => $load, active => $active, sub => $sub, desc => $desc }; } return $results; } sub run { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->output_add(long_msg => "'" . $name . "' [desc = " . $results->{$name}->{desc} . '] [load = ' . $results->{$name}->{load} . '] [active = ' . $results->{$name}->{active} . '] [sub = ' . $results->{$name}->{sub} . ']'); } $self->{output}->output_add( severity => 'OK', short_msg => 'List systemd services:' ); $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); $self->{output}->exit(); } sub disco_format { my ($self, %options) = @_; $self->{output}->add_disco_format(elements => ['name', 'description', 'load', 'active', 'sub']); } sub disco_show { my ($self, %options) = @_; my $results = $self->manage_selection(custom => $options{custom}); foreach my $name (sort(keys %$results)) { $self->{output}->add_disco_entry( name => $name, description => $results->{$name}->{desc}, load => $results->{$name}->{load}, active => $results->{$name}->{active}, sub => $results->{$name}->{sub} ); } } 1; =head1 MODE List systemd services. Command used: systemctl -a --no-pager --no-legend --plain Command change for systemctl version >= 248 : --no-legend is converted in legend=false =over 8 =item B<--filter-name> Filter services name (regexp can be used). =item B<--filter-description> Filter services description (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_LISTSYSTEMDSERVICES $fatpacked{"os/linux/local/mode/loadaverage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LOADAVERAGE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::loadaverage; use base qw(centreon::plugins::mode); use strict; use warnings; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'warning:s' => { name => 'warning', default => '' }, 'critical:s' => { name => 'critical', default => '' }, 'average' => { name => 'average' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); ($self->{warn1}, $self->{warn5}, $self->{warn15}) = split /,/, $self->{option_results}->{warning}; ($self->{crit1}, $self->{crit5}, $self->{crit15}) = split /,/, $self->{option_results}->{critical}; if (($self->{perfdata}->threshold_validate(label => 'warn1', value => $self->{warn1})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning (1min) threshold '" . $self->{warn1} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'warn5', value => $self->{warn5})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning (5min) threshold '" . $self->{warn5} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'warn15', value => $self->{warn15})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning (15min) threshold '" . $self->{warn15} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'crit1', value => $self->{crit1})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical (1min) threshold '" . $self->{crit1} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'crit5', value => $self->{crit5})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical (5min) threshold '" . $self->{crit5} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'crit15', value => $self->{crit15})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical (15min) threshold '" . $self->{crit15} . "'."); $self->{output}->option_exit(); } } sub run { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'tail', command_options => '-n +1 /proc/loadavg /proc/stat 2>&1' ); my ($load1m, $load5m, $load15m); my ($msg, $cpu_load1, $cpu_load5, $cpu_load15); if ($stdout =~ /\/proc\/loadavg.*?([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/ms) { ($load1m, $load5m, $load15m) = ($1, $2, $3) } if (!defined($load1m) || !defined($load5m) || !defined($load15m)) { $self->{output}->add_option_msg(short_msg => "Some informations missing."); $self->{output}->option_exit(); } if (defined($self->{option_results}->{average})) { my $countCpu = 0; $countCpu++ while ($stdout =~ /^cpu\d+/msg); if ($countCpu == 0){ $self->{output}->output_add(severity => 'unknown', short_msg => 'Unable to get number of CPUs'); $self->{output}->display(); $self->{output}->exit(); } $cpu_load1 = sprintf("%0.2f", $load1m / $countCpu); $cpu_load5 = sprintf("%0.2f", $load5m / $countCpu); $cpu_load15 = sprintf("%0.2f", $load15m / $countCpu); $msg = sprintf("Load average: %s [%s/%s CPUs], %s [%s/%s CPUs], %s [%s/%s CPUs]", $cpu_load1, $load1m, $countCpu, $cpu_load5, $load5m, $countCpu, $cpu_load15, $load15m, $countCpu); $self->{output}->perfdata_add( label => 'avg_load1', value => $cpu_load1, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn1'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit1'), min => 0 ); $self->{output}->perfdata_add( label => 'avg_load5', value => $cpu_load5, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn5'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit5'), min => 0 ); $self->{output}->perfdata_add( label => 'avg_load15', value => $cpu_load15, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn15'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit15'), min => 0 ); $self->{output}->perfdata_add( label => 'load1', value => $load1m, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn1', op => '*', value => $countCpu), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit1', op => '*', value => $countCpu), min => 0 ); $self->{output}->perfdata_add( label => 'load5', value => $load5m, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn5', op => '*', value => $countCpu), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit5', op => '*', value => $countCpu), min => 0 ); $self->{output}->perfdata_add( label => 'load15', value => $load15m, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn15', op => '*', value => $countCpu), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit15', op => '*', value => $countCpu), min => 0 ); } else { $cpu_load1 = $load1m; $cpu_load5 = $load5m; $cpu_load15 = $load15m; $msg = sprintf("Load average: %s, %s, %s", $cpu_load1, $cpu_load5, $cpu_load15); $self->{output}->perfdata_add( label => 'load1', value => $cpu_load1, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn1'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit1'), min => 0 ); $self->{output}->perfdata_add( label => 'load5', value => $cpu_load5, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn5'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit5'), min => 0 ); $self->{output}->perfdata_add( label => 'load15', value => $cpu_load15, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warn15'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'crit15'), min => 0 ); } my $exit1 = $self->{perfdata}->threshold_check(value => $cpu_load1, threshold => [ { label => 'crit1', 'exit_litteral' => 'critical' }, { label => 'warn1', exit_litteral => 'warning' } ]); my $exit2 = $self->{perfdata}->threshold_check(value => $cpu_load5, threshold => [ { label => 'crit5', 'exit_litteral' => 'critical' }, { label => 'warn5', exit_litteral => 'warning' } ]); my $exit3 = $self->{perfdata}->threshold_check(value => $cpu_load15, threshold => [ { label => 'crit15', 'exit_litteral' => 'critical' }, { label => 'warn15', exit_litteral => 'warning' } ]); my $exit = $self->{output}->get_most_critical(status => [ $exit1, $exit2, $exit3 ]); $self->{output}->output_add( severity => $exit, short_msg => $msg ); $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check system load-average. (need '/proc/loadavg' file). Command used: tail -n +1 /proc/loadavg /proc/stat 2>&1 =over 8 =item B<--warning> Warning threshold (1min,5min,15min). =item B<--critical> Critical threshold (1min,5min,15min). =item B<--average> Load average for the number of CPUs. =back =cut OS_LINUX_LOCAL_MODE_LOADAVERAGE $fatpacked{"os/linux/local/mode/lvm.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_LVM'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::lvm; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::misc; sub custom_space_usage_output { my ($self, %options) = @_; my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); return sprintf( 'space usage total: %s used: %s (%.2f%%) free: %s (%.2f%%)', $total_size_value . " " . $total_size_unit, $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free} ); } sub prefix_vg_output { my ($self, %options) = @_; return sprintf( "VG '%s' ", $options{instance_value}->{name} ); } sub prefix_dlv_output { my ($self, %options) = @_; return sprintf( "direct LV '%s' [VG: %s] ", $options{instance_value}->{lvName}, $options{instance_value}->{vgName} ); } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0 }, { name => 'vg', type => 1, cb_prefix_output => 'prefix_vg_output', message_multiple => 'All VGs are ok' }, { name => 'dlv', type => 1, cb_prefix_output => 'prefix_dlv_output', message_multiple => 'All direct LVs are ok' } ]; $self->{maps_counters}->{global} = [ { label => 'lv-detected', display_ok => 0, nlabel => 'lv.detected.count', set => { key_values => [ { name => 'lv_detected' } ], output_template => 'number of direct LV detected: %s', perfdatas => [ { template => '%s', min => 0 } ] } }, { label => 'vg-detected', display_ok => 0, nlabel => 'vg.detected.count', set => { key_values => [ { name => 'vg_detected' } ], output_template => 'number of direct VG detected: %s', perfdatas => [ { template => '%s', min => 0 } ] } } ]; $self->{maps_counters}->{dlv} = [ { label => 'lv-data-usage', nlabel => 'lv.data.usage.percentage', set => { key_values => [ { name => 'data' }, { name => 'vgName' }, { name => 'lvName' } ], output_template => 'data usage: %.2f %%', closure_custom_perfdata => sub { my ($self, %options) = @_; $self->{output}->perfdata_add( nlabel => $self->{nlabel}, unit => '%', instances => [$self->{result_values}->{vgName}, $self->{result_values}->{lvName}], value => sprintf('%.2f', $self->{result_values}->{data}), warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), min => 0, max => 100 ); } } }, { label => 'lv-meta-usage', nlabel => 'lv.meta.usage.percentage', set => { key_values => [ { name => 'meta' }, { name => 'vgName' }, { name => 'lvName' } ], output_template => 'meta usage: %.2f %%', closure_custom_perfdata => sub { my ($self, %options) = @_; $self->{output}->perfdata_add( nlabel => $self->{nlabel}, unit => '%', instances => [$self->{result_values}->{vgName}, $self->{result_values}->{lvName}], value => sprintf('%.2f', $self->{result_values}->{meta}), warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), min => 0, max => 100 ); } } } ]; $self->{maps_counters}->{vg} = [ { label => 'vg-space-usage', nlabel => 'vg.space.usage.bytes', set => { key_values => [ { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_space_usage_output'), perfdatas => [ { template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1, label_extra_instance => 1 } ] } }, { label => 'vg-space-usage-free', nlabel => 'vg.space.free.bytes', display_ok => 0, set => { key_values => [ { name => 'free' }, { name => 'used' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_space_usage_output'), perfdatas => [ { template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1, label_extra_instance => 1 } ] } }, { label => 'vg-space-usage-prct', nlabel => 'vg.space.usage.percentage', display_ok => 0, set => { key_values => [ { name => 'prct_used' }, { name => 'used' }, { name => 'free' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_space_usage_output'), perfdatas => [ { template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-lv:s' => { name => 'filter_lv' }, 'filter-vg:s' => { name => 'filter_vg' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout, $exit_code) = $options{custom}->execute_command( command => 'lvs', command_options => '--separator=, 2>&1', no_quit => 1 ); $self->{global} = { lv_detected => 0, vg_detected => 0 }; $self->{dlv} = {}; # LV,VG,Attr,LSize,Pool,Origin,Data%,Meta%,Move,Log,Cpy%Sync,Convert # thinpool,docker,twi-aot---,71.25g,,,1.95,0.06,,,, # lv_controlm,vg_sys,-wi-ao----,5.00g,,,,,,,, my @lines = split(/\n/, $stdout); shift @lines; my $i = 0; foreach my $line (@lines) { my @fields = split(/,/, $line); next if (!defined($fields[6]) || $fields[6] !~ /[0-9]/); my ($vg, $lv, $data, $meta) = (centreon::plugins::misc::trim($fields[1]), centreon::plugins::misc::trim($fields[0]), $fields[6], $fields[7]); next if (defined($self->{option_results}->{filter_lv}) && $self->{option_results}->{filter_lv} ne '' && $lv !~ /$self->{option_results}->{filter_lv}/); next if (defined($self->{option_results}->{filter_vg}) && $self->{option_results}->{filter_vg} ne '' && $vg !~ /$self->{option_results}->{filter_vg}/); $self->{lv_detected}++; $self->{dlv}->{$i} = { lvName => $lv, vgName => $vg, data => $data, meta => $meta }; $i++; } ($stdout, $exit_code) = $options{custom}->execute_command( command => 'vgs', command_options => '--separator=, --units=b 2>&1', no_quit => 1 ); # VG,#PV,#LV,#SN,Attr,VSize,VFree # test,1,1,0,wz--n-,5364514816B,3217031168B $self->{vg} = {}; @lines = split(/\n/, $stdout); shift @lines; foreach my $line (@lines) { my @fields = split(/,/, $line); next if (!defined($fields[5]) || $fields[5] !~ /[0-9]/); my $vg = centreon::plugins::misc::trim($fields[0]); $fields[5] =~ /^(\d+)/; my $size = $1; $fields[6] =~ /^(\d+)/; my $free = $1; next if (defined($self->{option_results}->{filter_vg}) && $self->{option_results}->{filter_vg} ne '' && $vg !~ /$self->{option_results}->{filter_vg}/); $self->{vg_detected}++; $self->{vg}->{$vg} = { name => $vg, total => $size, free => $free, used => $size - $free, prct_free => $free * 100 / $size, prct_used => ($size - $free) * 100 / $size }; } } 1; =head1 MODE Check direct LV and VG free space. Command used: lvs --separator="," 2>&1 =over 8 =item B<--filter-vg> Filter volume group (regexp can be used). =item B<--filter-lv> Filter logical volume (regexp can be used). =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'lv-detected', 'vg-detected', 'vg-space-usage', 'vg-space-usage-free', 'vg-space-usage-prct', 'lv-data-usage', 'lv-meta-usage'. =back =cut OS_LINUX_LOCAL_MODE_LVM $fatpacked{"os/linux/local/mode/memory.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_MEMORY'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::memory; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub custom_memory_output { my ($self, %options) = @_; return sprintf( 'Ram total: %s %s used (-%s): %s %s (%.2f%%) free: %s %s (%.2f%%) available: %s %s (%.2f%%)', $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}), $self->{result_values}->{used_desc}, $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}), $self->{result_values}->{prct_used}, $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}), $self->{result_values}->{prct_free}, $self->{perfdata}->change_bytes(value => $self->{result_values}->{available}), $self->{result_values}->{prct_available}, ); } sub custom_swap_output { my ($self, %options) = @_; return sprintf( 'Swap total: %s %s used: %s %s (%.2f%%) free: %s %s (%.2f%%)', $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}), $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}), $self->{result_values}->{prct_used}, $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}), $self->{result_values}->{prct_free} ); } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'memory', type => 0, skipped_code => { -10 => 1 } }, { name => 'swap', type => 0, skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{memory} = [ { label => 'memory-usage', nlabel => 'memory.usage.bytes', set => { key_values => [ { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' }, { name => 'used_desc' }, { name => 'available' }, { name => 'prct_available' } ], closure_custom_output => $self->can('custom_memory_output'), perfdatas => [ { template => '%d', min => 0, max => 'total', unit => 'B' } ] } }, { label => 'memory-usage-free', nlabel => 'memory.free.bytes', display_ok => 0, set => { key_values => [ { name => 'free' }, { name => 'used' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' }, { name => 'used_desc' }, { name => 'available' }, { name => 'prct_available' } ], closure_custom_output => $self->can('custom_memory_output'), perfdatas => [ { template => '%d', min => 0, max => 'total', unit => 'B' } ] } }, { label => 'memory-usage-prct', nlabel => 'memory.usage.percentage', display_ok => 0, set => { key_values => [ { name => 'prct_used' }, { name => 'used' }, { name => 'free' }, { name => 'prct_free' }, { name => 'total' }, { name => 'used_desc' }, { name => 'available' }, { name => 'prct_available' } ], closure_custom_output => $self->can('custom_memory_output'), perfdatas => [ { template => '%.2f', min => 0, max => 100, unit => '%' } ] } }, { label => 'memory-available', nlabel => 'memory.available.bytes', display_ok => 0, set => { key_values => [ { name => 'available' }, { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' }, { name => 'used_desc' }, { name => 'prct_available' } ], closure_custom_output => $self->can('custom_memory_output'), perfdatas => [ { template => '%d', min => 0, max => 'total', unit => 'B' } ] } }, { label => 'memory-available-prct', nlabel => 'memory.available.percentage', display_ok => 0, set => { key_values => [ { name => 'prct_available' }, { name => 'prct_used' }, { name => 'used' }, { name => 'free' }, { name => 'prct_free' }, { name => 'total' }, { name => 'used_desc' }, { name => 'available' } ], closure_custom_output => $self->can('custom_memory_output'), perfdatas => [ { template => '%.2f', min => 0, max => 100, unit => '%' } ] } }, { label => 'buffer', nlabel => 'memory.buffer.bytes', set => { key_values => [ { name => 'buffer' } ], output_template => 'buffer: %s %s', output_change_bytes => 1, perfdatas => [ { template => '%d', min => 0, unit => 'B' } ] } }, { label => 'cached', nlabel => 'memory.cached.bytes', set => { key_values => [ { name => 'cached' } ], output_template => 'cached: %s %s', output_change_bytes => 1, perfdatas => [ { template => '%d', min => 0, unit => 'B' } ] } }, { label => 'slab', nlabel => 'memory.slab.bytes', set => { key_values => [ { name => 'slab' } ], output_template => 'slab: %s %s', output_change_bytes => 1, perfdatas => [ { template => '%d', min => 0, unit => 'B' } ] } }, ]; $self->{maps_counters}->{swap} = [ { label => 'swap', nlabel => 'swap.usage.bytes', set => { key_values => [ { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_swap_output'), perfdatas => [ { label => 'swap', template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1 } ] } }, { label => 'swap-free', display_ok => 0, nlabel => 'swap.free.bytes', set => { key_values => [ { name => 'free' }, { name => 'used' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_swap_output'), perfdatas => [ { label => 'swap_free', template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1 } ] } }, { label => 'swap-prct', display_ok => 0, nlabel => 'swap.usage.percentage', set => { key_values => [ { name => 'prct_used' }, { name => 'used' }, { name => 'free' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_swap_output'), perfdatas => [ { label => 'swap_prct', template => '%.2f', min => 0, max => 100, unit => '%' } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'swap' => { name => 'check_swap' }, 'warning:s' => { name => 'warning', redirect => 'warning-memory-usage-percentage' }, 'critical:s' => { name => 'critical', redirect => 'critical-memory-usage-percentage' } }); return $self; } sub check_rhel_version { my ($self, %options) = @_; $self->{rhel_71} = 0; return if ($options{stdout} !~ /(?:Redhat|CentOS|Red[ \-]Hat).*?release\s+(\d+)\.(\d+)/mi); $self->{rhel_71} = 1 if ($1 >= 8 || ($1 == 7 && $2 >= 1)); } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/meminfo /etc/redhat-release 2>&1', no_quit => 1 ); # Buffer can be missing. In Openvz container for example. my $buffer_used = 0; my $available = 0; my ($cached_used, $free, $total_size, $slab_used, $swap_total, $swap_free); foreach (split(/\n/, $stdout)) { if (/^MemTotal:\s+(\d+)/i) { $total_size = $1 * 1024; } elsif (/^Cached:\s+(\d+)/i) { $cached_used = $1 * 1024; } elsif (/^Buffers:\s+(\d+)/i) { $buffer_used = $1 * 1024; } elsif (/^Slab:\s+(\d+)/i) { $slab_used = $1 * 1024; } elsif (/^MemFree:\s+(\d+)/i) { $free = $1 * 1024; } elsif (/^SwapTotal:\s+(\d+)/i) { $swap_total = $1 * 1024; } elsif (/^SwapFree:\s+(\d+)/i) { $swap_free = $1 * 1024; } elsif (/^MemAvailable:\s+(\d+)/i) { $available = $1 * 1024; } } if (!defined($total_size) || !defined($cached_used) || !defined($free)) { $self->{output}->add_option_msg(short_msg => 'Some informations missing.'); $self->{output}->option_exit(); } $self->check_rhel_version(stdout => $stdout); my $physical_used = $total_size - $free; my $nobuf_used = $physical_used - $buffer_used - $cached_used; if ($self->{rhel_71} == 1) { $nobuf_used -= $slab_used if (defined($slab_used)); } my $used_desc = 'buffers/cache'; $used_desc .= '/slab' if ($self->{rhel_71} == 1 && defined($slab_used)); $self->{memory} = { total => $total_size, used => $nobuf_used, free => $total_size - $nobuf_used, prct_used => $nobuf_used * 100 / $total_size, prct_free => 100 - ($nobuf_used * 100 / $total_size), used_desc => $used_desc, available => $available, prct_available => $available * 100 / $total_size, buffer => $buffer_used, cache => $cached_used, slab => $slab_used }; if (defined($self->{option_results}->{check_swap}) && defined($swap_total) && $swap_total > 0) { $self->{swap} = { total => $swap_total, used => $swap_total - $swap_free, free => $swap_free, prct_used => 100 - ($swap_free * 100 / $swap_total), prct_free => ($swap_free * 100 / $swap_total) }; } } 1; =head1 MODE Check physical memory (need '/proc/meminfo' file). Command used: cat /proc/meminfo /etc/redhat-release 2>&1 =over 8 =item B<--swap> Check swap also. =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'memory-usage' (B), 'memory-usage-free' (B), 'memory-usage-prct' (%), 'memory-available' (B), 'memory-available-prct' (%), 'swap' (B), 'swap-free' (B), 'swap-prct' (%), 'buffer' (B), 'cached' (B), 'slab' (B). =back =cut OS_LINUX_LOCAL_MODE_MEMORY $fatpacked{"os/linux/local/mode/mountpoint.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_MOUNTPOINT'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::mountpoint; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); sub custom_status_output { my ($self, %options) = @_; return "options are '" . $self->{result_values}->{options} . "' [type: " . $self->{result_values}->{type} . "]"; } sub prefix_output { my ($self, %options) = @_; return "Mount point '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'mountpoints', type => 1, cb_prefix_output => 'prefix_output', message_multiple => 'All mount points options are ok' } ]; $self->{maps_counters}->{mountpoints} = [ { label => 'status', type => 2, critical_default => '%{options} !~ /^rw/i && %{type} !~ /tmpfs|squashfs/i', set => { key_values => [ { name => 'display' }, { name => 'options' }, { name => 'type' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-device:s' => { name => 'filter_device' }, 'exclude-device:s' => { name => 'exclude_device' }, 'filter-mountpoint:s' => { name => 'filter_mountpoint' }, 'exclude-mountpoint:s' => { name => 'exclude_mountpoint' }, 'filter-type:s' => { name => 'filter_type' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'mount', command_options => '2>&1', no_quit => 1 ); $self->{mountpoints} = {}; my @lines = split /\n/, $stdout; foreach my $line (@lines) { next if ($line !~ /^\s*(.*?)\s+on\s+(.*?)\s+type\s+(\S+)\s+\((.*)\)/); my ($device, $mountpoint, $type, $options) = ($1, $2, $3, $4); next if (defined($self->{option_results}->{filter_type}) && $self->{option_results}->{filter_type} ne '' && $type !~ /$self->{option_results}->{filter_type}/); next if (defined($self->{option_results}->{filter_device}) && $self->{option_results}->{filter_device} ne '' && $device !~ /$self->{option_results}->{filter_device}/); next if (defined($self->{option_results}->{exclude_device}) && $self->{option_results}->{exclude_device} ne '' && $device =~ /$self->{option_results}->{exclude_device}/); next if (defined($self->{option_results}->{filter_mountpoint}) && $self->{option_results}->{filter_mountpoint} ne '' && $mountpoint !~ /$self->{option_results}->{filter_mountpoint}/); next if (defined($self->{option_results}->{exclude_mountpoint}) && $self->{option_results}->{exclude_mountpoint} ne '' && $mountpoint =~ /$self->{option_results}->{exclude_mountpoint}/); $self->{mountpoints}->{$mountpoint} = { display => $mountpoint, type => $type, options => $options }; } if (scalar(keys %{$self->{mountpoints}}) <= 0) { $self->{output}->add_option_msg(short_msg => 'No mount points found'); $self->{output}->option_exit(); } } 1; =head1 MODE Check mount points options. Command used: mount 2>&1 =over 8 =item B<--filter-mountpoint> Filter mount point name (can use regexp). =item B<--exclude-mountpoint> Exclude mount point name (can use regexp). =item B<--filter-device> Filter device name (can use regexp). =item B<--exclude-device> Exclude device name (can use regexp). =item B<--filter-type> Filter mount point type (can use regexp). =item B<--warning-status> Warning threshold. =item B<--critical-status> Critical threshold (default: '%{options} !~ /^rw/i && %{type} !~ /tmpfs|squashfs/i'). =back =cut OS_LINUX_LOCAL_MODE_MOUNTPOINT $fatpacked{"os/linux/local/mode/ntp.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_NTP'; # # Copyright 2018 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::ntp; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::misc; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); my %state_map_ntpq = ( '<sp>' => 'discarded due to high stratum and/or failed sanity checks', 'x' => 'designated falsticker by the intersection algorithm', '.' => 'culled from the end of the candidate list', '-' => 'discarded by the clustering algorithm', '+' => 'included in the final selection set', '#' => 'selected for synchronization but distance exceeds maximum', '*' => 'selected for synchronization', 'o' => 'selected for synchronization, PPS signal in use' ); my %type_map_ntpq = ( 'l' => 'local', 'u' => 'unicast', 'm' => 'multicast', 'b' => 'broadcast', '-' => 'netaddr' ); my %state_map_chronyc = ( 'x' => 'time may be in error', '-' => 'not combined', '+' => 'combined', '?' => 'unreachable', '*' => 'current synced', '~' => 'time too variable' ); my %type_map_chronyc = ( '^' => 'server', '=' => 'peer', '#' => 'local clock' ); my %unit_map_chronyc = ( 'ns' => 0.000001, 'us' => 0.001, 'ms' => 1, 's' => 1000 ); sub custom_status_output { my ($self, %options) = @_; return sprintf( '[type: %s] [reach: %s] [state: %s]', $self->{result_values}->{type}, $self->{result_values}->{reach}, $self->{result_values}->{state} ); } sub custom_offset_perfdata { my ($self, %options) = @_; if ($self->{result_values}->{rawstate} ne '*') { $self->{output}->perfdata_add( nlabel => $self->{nlabel}, unit => 'ms', instances => $self->{result_values}->{display}, value => $self->{result_values}->{offset}, min => 0 ); } else { $self->{output}->perfdata_add( nlabel => $self->{nlabel}, unit => 'ms', instances => $self->{result_values}->{display}, value => $self->{result_values}->{offset}, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), min => 0 ); } } sub custom_offset_threshold { my ($self, %options) = @_; if ($self->{result_values}->{rawstate} ne '*') { return 'ok'; } return $self->{perfdata}->threshold_check(value => $self->{result_values}->{offset}, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' } ]); } sub prefix_peer_output { my ($self, %options) = @_; return "Peer '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0 }, { name => 'peers', type => 1, cb_prefix_output => 'prefix_peer_output', message_multiple => 'All peers are ok' } ]; $self->{maps_counters}->{global} = [ { label => 'peers', nlabel => 'peers.detected.count', set => { key_values => [ { name => 'peers' } ], output_template => 'Number of ntp peers: %d', perfdatas => [ { label => 'peers', template => '%d', min => 0 } ] } } ]; $self->{maps_counters}->{peers} = [ { label => 'status', type => 2, set => { key_values => [ { name => 'rawstate' }, { name => 'rawtype' }, { name => 'state' }, { name => 'type' }, { name => 'reach' }, { name => 'display' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } }, { label => 'offset', nlabel => 'peer.time.offset.milliseconds', display_ok => 0, set => { key_values => [ { name => 'offset' }, { name => 'rawstate' }, { name => 'display' } ], output_template => 'offset: %s ms', closure_custom_threshold_check => $self->can('custom_offset_threshold'), closure_custom_perfdata => $self->can('custom_offset_perfdata'), perfdatas => [ { template => '%s', min => 0, unit => 'ms', label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'stratum', nlabel => 'peer.stratum.count', display_ok => 0, set => { key_values => [ { name => 'stratum' }, { name => 'display' } ], output_template => 'stratum: %s', perfdatas => [ { label => 'stratum', template => '%s', min => 0, label_extra_instance => 1, instance_use => 'display' } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'ntp-mode:s' => { name => 'ntp_mode', default => 'ntpq' }, 'filter-name:s' => { name => 'filter_name' }, 'filter-state:s' => { name => 'filter_state' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); if ($self->{option_results}->{ntp_mode} !~ /^(?:ntpq|chronyc|all)$/) { $self->{output}->add_option_msg(short_msg => "ntp mode '" . $self->{option_results}->{ntp_mode} . "' not implemented" ); $self->{output}->option_exit(); } } sub get_ntp_modes { my ($self, %options) = @_; my $modes = { ntpq => { regexp => '^(\+|\*|\.|\-|\#|x|\<sp\>|o)(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', command => 'ntpq', command_options => '-p -n 2>&1', type => 'ntpq' }, chronyc => { regexp => '^(.)(\+|\*|\.|\-|\#|x|\<sp\>)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*?)(\d+)(\w+)$', command => 'chronyc', command_options => '-n sources 2>&1', type => 'chronyc' } }; if ($self->{option_results}->{ntp_mode} eq 'ntpq') { return [ $modes->{ntpq} ]; } elsif ($self->{option_results}->{ntp_mode} eq 'chronyc') { return [ $modes->{chronyc} ]; } return [ $modes->{chronyc}, $modes->{ntpq} ]; } sub manage_selection { my ($self, %options) = @_; my $modes = $self->get_ntp_modes(); $self->{global} = { peers => 0 }; $self->{peers} = {}; foreach my $mode (@$modes) { my ($stdout) = $options{custom}->execute_command( command => $mode->{command}, command_options => $mode->{command_options}, no_quit => $self->{option_results}->{ntp_mode} eq 'all' ? 1 : undef ); my @lines = split(/\n/, $stdout); foreach my $line (@lines) { if ($self->{option_results}->{ntp_mode} ne 'all' && $line =~ /Connection refused/) { $self->{output}->add_option_msg(short_msg => "check ntp.conf and ntp daemon" ); $self->{output}->option_exit(); } next if ($line !~ /$mode->{regexp}/); my $entry = {}; my ($remote_peer, $peer_fate) = (centreon::plugins::misc::trim($2), centreon::plugins::misc::trim($1)); if ($mode->{type} eq 'chronyc') { $remote_peer = centreon::plugins::misc::trim($3); $peer_fate = centreon::plugins::misc::trim($2); my ($type, $stratum, $poll, $reach, $lastRX, $offset) = ($1, $4, $5, $6, $7, $9); $entry = { display => $remote_peer, rawstate => $peer_fate, state => $state_map_chronyc{$peer_fate}, stratum => centreon::plugins::misc::trim($stratum), rawtype => centreon::plugins::misc::trim($type), type => $type_map_chronyc{centreon::plugins::misc::trim($type)}, reach => centreon::plugins::misc::trim($reach), offset => centreon::plugins::misc::trim($offset) * $unit_map_chronyc{centreon::plugins::misc::trim($10)}, }; } else { my ($refid, $stratum, $type, $last_time, $polling_intervall, $reach, $delay, $offset, $jitter) = ($3, $4, $5, $6, $7, $8, $9, $10, $11); $entry = { display => $remote_peer, rawstate => $peer_fate, state => $state_map_ntpq{$peer_fate}, stratum => centreon::plugins::misc::trim($stratum), rawtype => centreon::plugins::misc::trim($type), type => $type_map_ntpq{centreon::plugins::misc::trim($type)}, reach => centreon::plugins::misc::trim($reach), offset => centreon::plugins::misc::trim($offset) }; } if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $remote_peer !~ /$self->{option_results}->{filter_name}/) { $self->{output}->output_add(long_msg => "skipping '" . $remote_peer . "': no matching filter peer name.", debug => 1); next; } if (defined($self->{option_results}->{filter_state}) && $self->{option_results}->{filter_state} ne '' && $peer_fate !~ /$self->{option_results}->{filter_state}/) { $self->{output}->output_add(long_msg => "skipping '" . $remote_peer . "': no matching filter peer state.", debug => 1); next; } if ($mode->{type} eq 'ntpq') { my ($refid, $stratum, $type, $last_time, $polling_intervall, $reach, $delay, $offset, $jitter) = ($3, $4, $5, $6, $7, $8, $9, $10, $11); $self->{peers}->{$remote_peer} = $entry; } elsif ($mode->{type} eq 'chronyc') { #210 Number of sources = 4 #MS Name/IP address Stratum Poll Reach LastRx Last sample #=============================================================================== #^+ 212.83.187.62 2 9 377 179 -715us[ -731us] +/- 50ms #^- 129.250.35.251 2 8 377 15 -82us[ -99us] +/- 96ms $self->{peers}->{$remote_peer} = $entry; } $self->{global}->{peers}++; } } } 1; =head1 MODE Check ntp daemons. Command used: 'ntpq -p -n 2>&1' or 'chronyc -n sources 2>&1' =over 8 =item B<--ntp-mode> Default mode for parsing and command: 'ntpq' (default), 'chronyc' or 'all'. =item B<--filter-name> Filter peer name (can be a regexp). =item B<--filter-state> Filter peer state (can be a regexp). =item B<--warning-peers> Warning threshold minimum amount of NTP-Server =item B<--critical-peers> Critical threshold minimum amount of NTP-Server =item B<--warning-offset> Warning threshold offset deviation value in milliseconds =item B<--critical-offset> Critical threshold offset deviation value in milliseconds =item B<--warning-stratum> Warning threshold. =item B<--critical-stratum> Critical threshold. =item B<--unknown-status> Define the conditions to match for the status to be UNKNOWN. You can use the following variables: %{state}, %{rawstate}, %{type}, %{rawtype}, %{reach}, %{display} =item B<--warning-status> Define the conditions to match for the status to be WARNING. You can use the following variables: %{state}, %{rawstate}, %{type}, %{rawtype}, %{reach}, %{display} =item B<--critical-status> Define the conditions to match for the status to be CRITICAL. You can use the following variables: %{state}, %{rawstate}, %{type}, %{rawtype}, %{reach}, %{display} =back =cut OS_LINUX_LOCAL_MODE_NTP $fatpacked{"os/linux/local/mode/openfiles.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_OPENFILES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::openfiles; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0 } ]; $self->{maps_counters}->{global} = [ { label => 'files-open', nlabel => 'system.files.open.count', set => { key_values => [ { name => 'openfiles' } ], output_template => 'current open files: %s', perfdatas => [ { template => '%s', min => 0 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-username:s' => { name => 'filter_username' }, 'filter-appname:s' => { name => 'filter_appname' }, 'filter-pid:s' => { name => 'filter_pid' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'lsof', command_options => '-a -d ^mem -d ^cwd -d ^rtd -d ^txt -d ^DEL 2>&1' ); $self->{global} = { openfiles => 0 }; my @lines = split /\n/, $stdout; shift @lines; foreach (@lines) { /^(\S+)\s+(\S+)\s+(\S+)/; my ($name, $pid, $user) = ($1, $2, $3); next if (defined($self->{option_results}->{filter_username}) && $self->{option_results}->{filter_username} ne '' && $user !~ /$self->{option_results}->{filter_username}/); next if (defined($self->{option_results}->{filter_appname}) && $self->{option_results}->{filter_appname} ne '' && $name !~ /$self->{option_results}->{filter_appname}/); next if (defined($self->{option_results}->{filter_pid}) && $self->{option_results}->{filter_pid} ne '' && $pid !~ /$self->{option_results}->{filter_pid}/); $self->{global}->{openfiles}++; } } 1; =head1 MODE Check open files. Command used: lsof -a -d ^mem -d ^cwd -d ^rtd -d ^txt -d ^DEL 2>&1 =over 8 =item B<--filter-appname> Filter application name (can be a regexp). =item B<--filter-username> Filter username name (can be a regexp). =item B<--filter-pid> Filter PID (can be a regexp). =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'files-open'. =back =cut OS_LINUX_LOCAL_MODE_OPENFILES $fatpacked{"os/linux/local/mode/packeterrors.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_PACKETERRORS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::packeterrors; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); use Digest::MD5 qw(md5_hex); sub custom_status_output { my ($self, %options) = @_; return sprintf('status : %s', $self->{result_values}->{status}); } sub custom_packet_output { my ($self, %options) = @_; return sprintf( 'Packet %s %s : %.2f %% (%s)', ucfirst($self->{result_values}->{type}), ucfirst($self->{result_values}->{label}), $self->{result_values}->{result_prct}, $self->{result_values}->{diff_value} ); } sub custom_packet_calc { my ($self, %options) = @_; $self->{result_values}->{type} = $options{extra_options}->{type}; $self->{result_values}->{label} = $options{extra_options}->{label_ref}; $self->{result_values}->{diff_value} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{type} . '_' . $self->{result_values}->{label}} - $options{old_datas}->{$self->{instance} . '_' . $self->{result_values}->{type} . '_' . $self->{result_values}->{label}}; my $diff_total = $options{new_datas}->{$self->{instance} . '_total_' . $self->{result_values}->{label}} - $options{old_datas}->{$self->{instance} . '_total_' . $self->{result_values}->{label}}; $self->{result_values}->{result_prct} = ($diff_total == 0) ? 0 : ($self->{result_values}->{diff_value} * 100 / $diff_total); $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; return 0; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'interface', type => 1, cb_prefix_output => 'prefix_interface_output', message_multiple => 'All interfaces are ok', skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{interface} = [ { label => 'status', type => 2, critical_default => '%{status} ne "RU"', set => { key_values => [ { name => 'status' }, { name => 'display' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } }, { label => 'in-discard', nlabel => 'interface.packets.in.discard.percentage', set => { key_values => [ { name => 'discard_in', diff => 1 }, { name => 'total_in', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_packet_calc'), closure_custom_calc_extra_options => { type => 'discard', label_ref => 'in' }, closure_custom_output => $self->can('custom_packet_output'), output_error_template => 'Discard In : %s', threshold_use => 'result_prct', perfdatas => [ { value => 'result_prct', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'out-discard', nlabel => 'interface.packets.out.discard.percentage', set => { key_values => [ { name => 'discard_out', diff => 1 }, { name => 'total_out', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_packet_calc'), closure_custom_calc_extra_options => { type => 'discard', label_ref => 'out' }, closure_custom_output => $self->can('custom_packet_output'), output_error_template => 'Discard Out : %s', threshold_use => 'result_prct', perfdatas => [ { value => 'result_prct', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'in-error', nlabel => 'interface.packets.in.error.percentage', set => { key_values => [ { name => 'error_in', diff => 1 }, { name => 'total_in', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_packet_calc'), closure_custom_calc_extra_options => { type => 'error', label_ref => 'in' }, closure_custom_output => $self->can('custom_packet_output'), output_error_template => 'Error In : %s', threshold_use => 'result_prct', perfdatas => [ { value => 'result_prct', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1, instance_use => 'display' } ] } }, { label => 'out-error', nlabel => 'interface.packets.out.error.percentage', set => { key_values => [ { name => 'error_out', diff => 1 }, { name => 'total_out', diff => 1 }, { name => 'display' } ], closure_custom_calc => $self->can('custom_packet_calc'), closure_custom_calc_extra_options => { type => 'error', label_ref => 'out' }, closure_custom_output => $self->can('custom_packet_output'), output_error_template => 'Error In : %s', threshold_use => 'result_prct', perfdatas => [ { value => 'result_prct', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1, instance_use => 'display' } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-state:s' => { name => 'filter_state', }, 'filter-interface:s' => { name => 'filter_interface' }, 'exclude-interface:s' => { name => 'exclude_interface' }, 'no-loopback' => { name => 'no_loopback' } }); return $self; } sub prefix_interface_output { my ($self, %options) = @_; return "Interface '" . $options{instance_value}->{display} . "' "; } sub do_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'ip', command_path => '/sbin', command_options => '-s addr 2>&1' ); my $mapping = { ifconfig => { get_interface => '^(\S+)(.*?)(\n\n|\n$)', total => 'RX packets:(\d+).*?TX packets:(\d+)', discard_in => 'RX packets:\d+\s+?errors:\d+\s+?dropped:(\d+)', discard_out => 'TX packets:\d+\s+?errors:\d+\s+?dropped:(\d+)', error_in => 'RX packets:\d+\s+?errors:(\d+)', error_out => 'TX packets:\d+\s+?errors:(\d+)', }, iproute => { get_interface => '^\d+:\s+(\S+)(.*?)(?=\n\d|\Z$)', total => 'RX:\s+bytes\s+packets.*?\d+\s+(\d+).*?TX:\s+bytes\s+packets.*?\d+\s+(\d+)', discard_in => 'RX:.*?dropped.*?\d+.*?\d+.*?\d+.*?(\d+)', discard_out => 'TX:.*?dropped.*?\d+.*?\d+.*?\d+.*?(\d+)', error_in => 'RX:.*?errors.*?\d+.*?\d+.*?(\d+)', error_out => 'TX:.*?errors.*?\d+.*?\d+.*?(\d+)', }, }; my $type = 'ifconfig'; if ($stdout =~ /^\d+:\s+\S+:\s+</ms) { $type = 'iproute'; } $self->{interface} = {}; while ($stdout =~ /$mapping->{$type}->{get_interface}/msg) { my ($interface_name, $values) = ($1, $2); my $states = ''; $states .= 'R' if ($values =~ /RUNNING|LOWER_UP/ms); $states .= 'U' if ($values =~ /UP/ms); $interface_name =~ s/:$//; next if (defined($self->{option_results}->{no_loopback}) && $values =~ /LOOPBACK/ms); next if (defined($self->{option_results}->{filter_state}) && $self->{option_results}->{filter_state} ne '' && $states !~ /$self->{option_results}->{filter_state}/); next if (defined($self->{option_results}->{filter_interface}) && $self->{option_results}->{filter_interface} ne '' && $interface_name !~ /$self->{option_results}->{filter_interface}/); next if (defined($self->{option_results}->{exclude_interface}) && $self->{option_results}->{exclude_interface} ne '' && $interface_name =~ /$self->{option_results}->{exclude_interface}/); $self->{interface}->{$interface_name} = { display => $interface_name, status => $states, }; if ($values =~ /$mapping->{$type}->{total}/msi) { $self->{interface}->{$interface_name}->{total_in} = $1; $self->{interface}->{$interface_name}->{total_out} = $2; } foreach ('discard_in', 'discard_out', 'error_in', 'error_out') { if ($values =~ /$mapping->{$type}->{$_}/msi) { $self->{interface}->{$interface_name}->{$_} = $1; } } } if (scalar(keys %{$self->{interface}}) <= 0) { $self->{output}->add_option_msg(short_msg => "No interface found."); $self->{output}->option_exit(); } } sub manage_selection { my ($self, %options) = @_; $self->do_selection(custom => $options{custom}); $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_interface}) ? md5_hex($self->{option_results}->{filter_interface}) : md5_hex('all')); } 1; =head1 MODE Check packets errors and discards on interfaces. Command used: /sbin/ip -s addr 2>&1 =over 8 =item B<--unknown-status> Define the conditions to match for the status to be UNKNOWN. You can use the following variables: %{status}, %{display} =item B<--warning-status> Define the conditions to match for the status to be WARNING. You can use the following variables: %{status}, %{display} =item B<--critical-status> Define the conditions to match for the status to be CRITICAL (default: '%{status} ne "RU"'). You can use the following variables: %%{status}, %{display} =item B<--warning-*> Warning threshold in percent of total packets. Can be: in-error, out-error, in-discard, out-discard =item B<--critical-*> Critical threshold in percent of total packets. Can be: in-error, out-error, in-discard, out-discard =item B<--filter-interface> Filter interface name (regexp can be used). =item B<--exclude-interface> Exclude interface name (regexp can be used). =item B<--filter-state> Filter filesystem type (regexp can be used). =item B<--no-loopback> Don't display loopback interfaces. =back =cut OS_LINUX_LOCAL_MODE_PACKETERRORS $fatpacked{"os/linux/local/mode/paging.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_PAGING'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::paging; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use Digest::MD5 qw(md5_hex); sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0, cb_prefix_output => 'prefix_global_output', skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{global} = [ { label => 'pgpgin', nlabel => 'system.pgpin.usage.bytespersecond', set => { key_values => [ { name => 'pgpgin', per_second => 1 } ], output_template => 'pgpgin : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pgpgin', template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'pgpgout', nlabel => 'system.pgpgout.usage.bytespersecond', set => { key_values => [ { name => 'pgpgout', per_second => 1 } ], output_template => 'pgpgout : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pgpgout', template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'pswpin', nlabel => 'system.pswpin.usage.bytespersecond', set => { key_values => [ { name => 'pswpin', per_second => 1 } ], output_template => 'pswpin : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pswpin', template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'pswpout', nlabel => 'system.pswpout.usage.bytespersecond', set => { key_values => [ { name => 'pswpout', per_second => 1 } ], output_template => 'pswpout : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pswpout', template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'pgfault', nlabel => 'system.pgfault.usage.bytespersecond', set => { key_values => [ { name => 'pgfault', per_second => 1 } ], output_template => 'pgfault : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pgfault', template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'pgmajfault', nlabel => 'system.pgmajfault.usage.bytespersecond', set => { key_values => [ { name => 'pgmajfault', per_second => 1 } ], output_template => 'pgmajfault : %s %s/s', output_change_bytes => 1, perfdatas => [ { label => 'pgmajfault', template => '%d', unit => 'B/s', min => 0 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); bless $self, $class; $options{options}->add_options(arguments => { }); return $self; } sub prefix_global_output { my ($self, %options) = @_; return 'Paging '; } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/vmstat 2>&1' ); $self->{global} = {}; $self->{global}->{pgpgin} = $stdout =~ /^pgpgin.*?(\d+)/msi ? $1 * 1024 : undef; $self->{global}->{pgpgout} = $stdout =~ /^pgpgout.*?(\d+)/msi ? $1 * 1024 : undef; $self->{global}->{pswpin} = $stdout =~ /^pswpin.*?(\d+)/msi ? $1 * 1024 : undef; $self->{global}->{pswpout} = $stdout =~ /^pswpout.*?(\d+)/msi ? $1 * 1024: undef; $self->{global}->{pgfault} = $stdout =~ /^pgfault.*?(\d+)/msi ? $1 * 1024: undef; $self->{global}->{pgmajfault} = $stdout =~ /^pgmajfault.*?(\d+)/msi ? $1 * 1014: undef; $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')); } 1; =head1 MODE Check paging informations. Command used: cat /proc/vmstat 2>&1 =over 8 =item B<--warning-*> Warning threshold. Can be: 'pgpgin', 'pgpgout', 'pswpin', 'pswpout', 'pgfault', 'pgmajfault'. =item B<--critical-*> Critical threshold. Can be: 'pgpgin', 'pgpgout', 'pswpin', 'pswpout', 'pgfault', 'pgmajfault'. =back =cut OS_LINUX_LOCAL_MODE_PAGING $fatpacked{"os/linux/local/mode/pendingupdates.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_PENDINGUPDATES'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::pendingupdates; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0, skipped_code => { -10 => 1 } }, { name => 'updates', type => 1 } ]; $self->{maps_counters}->{global} = [ { label => 'total', nlabel => 'pending.updates.total.count', set => { key_values => [ { name => 'total' } ], output_template => 'Number of pending updates : %d', perfdatas => [ { label => 'total', template => '%d', min => 0 } ] } }, { label => 'security', nlabel => 'security.updates.total.count', set => { key_values => [ { name => 'total_security' } ], output_template => 'Number of pending security updates : %d', perfdatas => [ { label => 'total_security', template => '%d', min => 0 } ] } } ]; $self->{maps_counters}->{updates} = [ { label => 'update', set => { key_values => [ { name => 'package' }, { name => 'version' }, { name => 'repository' } ], closure_custom_output => $self->can('custom_updates_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => sub { return 'ok'; } } } ]; } sub custom_updates_output { my ($self, %options) = @_; return sprintf( "Package '%s' [version: %s] [repository: %s]", $self->{result_values}->{package}, $self->{result_values}->{version}, $self->{result_values}->{repository} ); } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'check-security' => { name => 'check_security' }, 'os-mode:s' => { name => 'os_mode', default => 'rhel' }, 'filter-package:s' => { name => 'filter_package' }, 'filter-repository:s' => { name => 'filter_repository' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); if ((defined($self->{option_results}->{os_mode}) && $self->{option_results}->{os_mode} ne 'rhel') && defined($self->{option_results}->{check_security})){ $self->{output}->add_option_msg(severity => 'UNKNOWN', short_msg => "--check-security is only available with rhel."); $self->{output}->option_exit(); } if (!defined($self->{option_results}->{os_mode}) || $self->{option_results}->{os_mode} eq '' || $self->{option_results}->{os_mode} eq 'rhel' ) { $self->{command} = 'yum'; $self->{command_options} = 'check-update 2>&1'; if (defined($self->{option_results}->{check_security})) { $self->{command_options} = '-q updateinfo list sec' } } elsif ($self->{option_results}->{os_mode} eq 'debian') { $self->{command} = 'apt-get'; $self->{command_options} = 'upgrade -sVq 2>&1'; } elsif ($self->{option_results}->{os_mode} eq 'suse') { $self->{command} = 'zypper'; $self->{command_options} = 'list-updates 2>&1'; } else { $self->{output}->add_option_msg(short_msg => "os mode '" . $self->{option_results}->{os_mode} . "' not implemented" ); $self->{output}->option_exit(); } } sub parse_updates { my ($self, %options) = @_; my @lines = split /\n/, $options{stdout}; foreach my $line (@lines) { next if ($line !~ /^(\S+)\s+(\d+\S+)\s+(\S+)/ && $line !~ /\s+(\S+)\s+\(\S+\s\=\>\s(\S+)\)/ && $line !~ /.*\|.*\|\s+(\S+)\s+\|.*\|\s+(\d+\S+)\s+\|.*/ && $line !~ /.*\|\s+(\S+)\s+\|\s+(\S+)\s+\|.*\|\s+(\d+\S+)\s+\|.*/); my ($package, $version, $repository) = ($1, $2, $3); if ($self->{option_results}->{os_mode} =~ /suse/i && $line =~ /.*\|\s+(\S+)\s+\|\s+(\S+)\s+\|.*\|\s+(\d+\S+)\s+\|.*/){ ($repository, $package, $version) = ($1, $2, $3); } $repository = "-" if (!defined($repository) || $repository eq ''); if (defined($self->{option_results}->{filter_package}) && $self->{option_results}->{filter_package} ne '' && $package !~ /$self->{option_results}->{filter_package}/) { $self->{output}->output_add(long_msg => "skipping '" . $package . "': no matching filter.", debug => 1); next; } if (defined($self->{option_results}->{filter_repository}) && $self->{option_results}->{filter_repository} ne '' && $repository !~ /$self->{option_results}->{filter_repository}/) { $self->{output}->output_add(long_msg => "skipping '" . $repository . "': no matching filter.", debug => 1); next; } $self->{updates}->{$package} = { package => $package, version => $version, repository => $repository, }; $self->{global}->{total}++; } } sub parse_security_updates { my ($self, %options) = @_; my @lines = split(/\n/, $options{stdout}); $self->{global}->{total_security} = 0; foreach my $line (@lines) { $self->{global}->{total_security}++; } } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => $self->{command}, command_options => $self->{command_options}, no_quit => 1 ); if ((defined($self->{option_results}->{check_security}) && (!defined($self->{option_results}->{os_mode}) || $self->{option_results}->{os_mode} eq '' || $self->{option_results}->{os_mode} eq 'rhel' ))){ $self->{global}->{total_security} = 0; parse_security_updates($self, stdout => $stdout); } else { $self->{global}->{total} = 0; $self->{updates} = {}; parse_updates($self, stdout => $stdout); } } 1; =head1 MODE Check pending updates. For rhel/centos: yum check-update 2>&1 For rhel/centos security: yum -q updateinfo list sec For Debian: apt-get upgrade -sVq 2>&1 For Suse: zypper list-updates 2>&1 =over 8 =item B<--os-mode> Default mode for parsing and command: 'rhel' (default), 'debian', 'suse'. =item B<--warning-total> Warning threshold for total amount of pending updates. =item B<--critical-total> Critical threshold for total amount of pending updates. =item B<--warning-security> Warning threshold for total amount of pending security updates. =item B<--critical-security> Critical threshold for total amount of pending security updates. =item B<--filter-package> Filter package name. =item B<--filter-repository> Filter repository name. =item B<--check-security> Display number of pending security updates. Only available for Red Hat-Based distributions. =back =cut OS_LINUX_LOCAL_MODE_PENDINGUPDATES $fatpacked{"os/linux/local/mode/process.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_PROCESS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::process; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::misc; use Digest::MD5 qw(md5_hex); use Time::HiRes; sub custom_cpu_calc { my ($self, %options) = @_; my $cpu_utime = $options{new_datas}->{$self->{instance} . '_cpu_utime'} - $options{old_datas}->{$self->{instance} . '_cpu_utime'}; my $cpu_stime = $options{new_datas}->{$self->{instance} . '_cpu_stime'} - $options{old_datas}->{$self->{instance} . '_cpu_stime'}; my $total_ticks = $options{delta_time} * $self->{instance_mode}->{option_results}->{clock_ticks}; $self->{result_values}->{cpu_prct} = 100 * ($cpu_utime + $cpu_stime) / $total_ticks; $self->{instance_mode}->{global}->{cpu_prct} += $self->{result_values}->{cpu_prct}; return 0; } sub custom_disks_calc { my ($self, %options) = @_; my $diff = $options{new_datas}->{$self->{instance} . '_disks_' . $options{extra_options}->{label_ref}} - $options{old_datas}->{$self->{instance} . '_disks_' . $options{extra_options}->{label_ref}}; $self->{result_values}->{'disks_' . $options{extra_options}->{label_ref}} = $diff / $options{delta_time}; $self->{instance_mode}->{global}->{'disks_' . $options{extra_options}->{label_ref}} += $self->{result_values}->{'disks_' . $options{extra_options}->{label_ref}}; return 0; } sub prefix_process_output { my ($self, %options) = @_; return sprintf( 'Process: [command => %s] [arg => %s] [state => %s] ', $options{instance_value}->{cmd}, $options{instance_value}->{args}, $options{instance_value}->{state} ); } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'processes', type => 1, cb_prefix_output => 'prefix_process_output', skipped_code => { -10 => 1, -1 => 1 } }, { name => 'global', type => 0, skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{global} = [ { label => 'total', nlabel => 'processes.total.count', set => { key_values => [ { name => 'processes' } ], output_template => 'Number of current processes: %s', perfdatas => [ { template => '%s', min => 0 } ] } }, { label => 'total-memory-usage', nlabel => 'processes.memory.usage.bytes', set => { key_values => [ { name => 'memory_used' } ], output_template => 'memory used: %s %s', output_change_bytes => 1, perfdatas => [ { template => '%s', min => 0, unit => 'B' } ] } }, { label => 'total-cpu-utilization', nlabel => 'processes.cpu.utilization.percentage', set => { key_values => [ { name => 'cpu_prct' } ], output_template => 'cpu usage: %.2f %%', output_change_bytes => 1, perfdatas => [ { template => '%.2f', unit => '%', min => 0 } ] } }, { label => 'total-disks-read', nlabel => 'processes.disks.io.read.usage.bytespersecond', set => { key_values => [ { name => 'disks_read' } ], output_template => 'disks read: %s %s/s', output_change_bytes => 1, perfdatas => [ { template => '%d', unit => 'B/s', min => 0 } ] } }, { label => 'total-disks-write', nlabel => 'processes.disks.io.write.usage.bytespersecond', set => { key_values => [ { name => 'disks_write' } ], output_template => 'disks write: %s %s/s', output_change_bytes => 1, perfdatas => [ { template => '%d', unit => 'B/s', min => 0 } ] } } ]; $self->{maps_counters}->{processes} = [ { label => 'time', set => { key_values => [ { name => 'duration_seconds' }, { name => 'duration_human' } ], output_template => 'duration: %s', output_use => 'duration_human', closure_custom_perfdata => sub { return 0; } } }, { label => 'memory-usage', set => { key_values => [ { name => 'memory_used' } ], output_template => 'memory used: %s %s', output_change_bytes => 1, closure_custom_perfdata => sub { return 0; } } }, { label => 'cpu-utilization', set => { key_values => [ { name => 'cpu_utime', diff => 1 }, { name => 'cpu_stime', diff => 1 } ], closure_custom_calc => $self->can('custom_cpu_calc'), output_template => 'cpu usage: %.2f %%', output_use => 'cpu_prct', threshold_use => 'cpu_prct', closure_custom_perfdata => sub { return 0; } } }, { label => 'disks-read', set => { key_values => [ { name => 'disks_read', diff => 1 } ], closure_custom_calc => $self->can('custom_disks_calc'), closure_custom_calc_extra_options => { label_ref => 'read' }, output_template => 'disks read: %s %s/s', output_change_bytes => 1, closure_custom_perfdata => sub { return 0; } } }, { label => 'disks-write', set => { key_values => [ { name => 'disks_write', diff => 1 } ], closure_custom_calc => $self->can('custom_disks_calc'), closure_custom_calc_extra_options => { label_ref => 'write' }, output_template => 'disks write: %s %s/s', output_change_bytes => 1, closure_custom_perfdata => sub { return 0; } } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-command:s' => { name => 'filter_command' }, 'exclude-command:s' => { name => 'exclude_command' }, 'filter-arg:s' => { name => 'filter_arg' }, 'exclude-arg:s' => { name => 'exclude_arg' }, 'filter-state:s' => { name => 'filter_state' }, 'filter-ppid:s' => { name => 'filter_ppid' }, 'add-cpu' => { name => 'add_cpu' }, 'add-memory' => { name => 'add_memory' }, 'add-disk-io' => { name => 'add_disk_io' }, 'page-size:s' => { name => 'page_size', default => 4096 }, 'clock-ticks:s' => { name => 'clock_ticks', default => 100 } }); return $self; } my $state_map = { Z => 'zombie', X => 'dead', W => 'paging', T => 'stopped', S => 'InterruptibleSleep', R => 'running', D => 'UninterrupibleSleep', I => 'IdleKernelThread' }; sub parse_output { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'ps', command_options => '-e -o state -o etime -o pid -o ppid -o comm:50 -o %a -w 2>&1' ); $self->{global} = { processes => 0 }; $self->{processes} = {}; my @lines = split(/\n/, $stdout); my $line = shift(@lines); foreach my $line (@lines) { next if ($line !~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.{50})\s+(.*)$/); my ($state, $elapsed, $pid, $ppid, $cmd, $args) = ( centreon::plugins::misc::trim($1), centreon::plugins::misc::trim($2), centreon::plugins::misc::trim($3), centreon::plugins::misc::trim($4), centreon::plugins::misc::trim($5), centreon::plugins::misc::trim($6) ); next if (defined($self->{option_results}->{filter_command}) && $self->{option_results}->{filter_command} ne '' && $cmd !~ /$self->{option_results}->{filter_command}/); next if (defined($self->{option_results}->{exclude_command}) && $self->{option_results}->{exclude_command} ne '' && $cmd =~ /$self->{option_results}->{exclude_command}/); next if (defined($self->{option_results}->{filter_arg}) && $self->{option_results}->{filter_arg} ne '' && $args !~ /$self->{option_results}->{filter_arg}/); next if (defined($self->{option_results}->{exclude_arg}) && $self->{option_results}->{exclude_arg} ne '' && $args =~ /$self->{option_results}->{exclude_arg}/); next if (defined($self->{option_results}->{filter_state}) && $self->{option_results}->{filter_state} ne '' && $state_map->{$state} !~ /$self->{option_results}->{filter_state}/i); next if (defined($self->{option_results}->{filter_ppid}) && $self->{option_results}->{filter_ppid} ne '' && $ppid !~ /$self->{option_results}->{filter_ppid}/); $args =~ s/\|//g; my $duration_seconds = $self->get_duration(elapsed => $elapsed); $self->{processes}->{$pid} = { ppid => $ppid, state => $state, duration_seconds => $duration_seconds, duration_human => centreon::plugins::misc::change_seconds(value => $duration_seconds), cmd => $cmd, args => $args }; $self->{global}->{processes}++; } } sub get_duration { my ($self, %options) = @_; # Format: [[dd-]hh:]mm:ss $options{elapsed} =~ /(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)/; my ($day, $hour, $min, $sec) = ($1, $2, $3, $4); my $total_seconds_elapsed = $sec + ($min * 60); if (defined($hour)) { $total_seconds_elapsed += ($hour * 60 * 60); } if (defined($day)) { $total_seconds_elapsed += ($day * 86400); } return $total_seconds_elapsed; } sub add_cpu { my ($self, %options) = @_; return if (!defined($self->{option_results}->{add_cpu})); $self->{global}->{cpu_prct} = 0; # /stat # utime (14) Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)) # stime (15) Amount of time that this process has been scheduled in kernel mode, measured in clock ticks foreach my $pid (keys %{$self->{processes}}) { next if ($options{content} !~ /==>\s*\/proc\/$pid\/stat\s+.*?\n(.*?)(?:==>|\Z)/ms); my @values = split(/\s+/, $1); $self->{processes}->{$pid}->{cpu_utime} = $values[13]; $self->{processes}->{$pid}->{cpu_stime} = $values[14]; } } sub add_memory { my ($self, %options) = @_; return if (!defined($self->{option_results}->{add_memory})); $self->{global}->{memory_used} = 0; # statm # resident (2) resident set size (inaccurate; same as VmRSS in /proc/[pid]/status) # data (6) data + stack # measured in page (default: 4096). can get with: getconf PAGESIZE foreach my $pid (keys %{$self->{processes}}) { next if ($options{content} !~ /==>\s*\/proc\/$pid\/statm.*?\n(.*?)(?:==>|\Z)/ms); my @values = split(/\s+/, $1); my $memory_used = ($values[1] * $self->{option_results}->{page_size}); $self->{processes}->{$pid}->{memory_used} = $memory_used; $self->{global}->{memory_used} += $memory_used; } } sub add_disk_io { my ($self, %options) = @_; return if (!defined($self->{option_results}->{add_disk_io})); $self->{global}->{disks_read} = 0; $self->{global}->{disks_write} = 0; # /io # read_bytes: 2256896 # write_bytes: 0 foreach my $pid (keys %{$self->{processes}}) { next if ($options{content} !~ /==>\s*\/proc\/$pid\/io\s+.*?\n(.*?)(?:==>|\Z)/ms); my $entries = $1; next if ($entries !~ /read_bytes:\s*(\d+)/m); $self->{processes}->{$pid}->{disks_read} = $1; next if ($entries !~ /write_bytes:\s*(\d+)/m); $self->{processes}->{$pid}->{disks_write} = $1; } } sub add_extra_metrics { my ($self, %options) = @_; my $files = []; push @$files, 'stat' if (defined($self->{option_results}->{add_cpu})); push @$files, 'statm' if (defined($self->{option_results}->{add_memory})); push @$files, 'io' if (defined($self->{option_results}->{add_disk_io})); my ($num_files, $files_arg) = (scalar(@$files), ''); return if ($num_files <= 0); $files_arg = $files->[0] if ($num_files == 1); $files_arg = '{' . join(',', @$files) . '}' if ($num_files > 1); my ($num_proc, $proc_arg) = (scalar(keys %{$self->{processes}}), ''); return if ($num_proc <= 0); $proc_arg = join(',', keys %{$self->{processes}}) if ($num_proc == 1); $proc_arg = '{' . join(',', keys %{$self->{processes}}) . '}' if ($num_proc > 1); $self->set_timestamp(timestamp => Time::HiRes::time()); my ($content) = $options{custom}->execute_command( command => 'bash', command_options => "-c 'tail -vn +1 /proc/$proc_arg/$files_arg'", no_quit => 1 ); $self->add_cpu(content => $content); $self->add_memory(content => $content); $self->add_disk_io(content => $content); } sub manage_selection { my ($self, %options) = @_; $self->{cache_name} = 'linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_command}) ? md5_hex($self->{option_results}->{filter_command}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_arg}) ? md5_hex($self->{option_results}->{filter_arg}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_state}) ? md5_hex($self->{option_results}->{filter_state}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_ppid}) ? md5_hex($self->{option_results}->{filter_ppid}) : md5_hex('all')); $self->parse_output(custom => $options{custom}); $self->add_extra_metrics(custom => $options{custom}); } 1; =head1 MODE Check linux processes. Command used: ps -e -o state -o ===%t===%p===%P=== -o comm:50 -o ===%a -w 2>&1 bash -c 'tail -n +1 /proc/{pid1,pid2,...}/{statm,stat,io}' =over 8 =item B<--add-cpu> Monitor CPU usage. =item B<--add-memory> Monitor memory usage. It's inaccurate but it provides a trend. =item B<--add-disk-io> Monitor disk I/O. =item B<--filter-command> Filter process commands (regexp can be used). =item B<--exclude-command> Exclude process commands (regexp can be used). =item B<--filter-arg> Filter process arguments (regexp can be used). =item B<--exclude-arg> Exclude process arguments (regexp can be used). =item B<--filter-ppid> Filter process ppid (regexp can be used). =item B<--filter-state> Filter process states (regexp can be used). You can use: 'zombie', 'dead', 'paging', 'stopped', 'InterrupibleSleep', 'running', 'UninterrupibleSleep'. =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'total', 'total-memory-usage', 'total-cpu-utilization', 'total-disks-read', 'total-disks-write', 'time', 'memory-usage', 'cpu-utilization', 'disks-read', 'disks-write'. =back =cut OS_LINUX_LOCAL_MODE_PROCESS $fatpacked{"os/linux/local/mode/quota.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_QUOTA'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::quota; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub custom_usage_perfdata { my ($self, %options) = @_; my $unit = ''; $unit = 'B' if ($self->{result_values}->{label_ref} eq 'data'); $self->{output}->perfdata_add( nlabel => $self->{nlabel}, unit => $unit, instances => $self->{result_values}->{display}, value => $self->{result_values}->{used}, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{result_values}->{warn_label}, total => $self->{result_values}->{total}, cast_int => 1), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{result_values}->{crit_label}, total => $self->{result_values}->{total}, cast_int => 1), min => 0 ); } sub custom_usage_threshold { my ($self, %options) = @_; my $exit = $self->{perfdata}->threshold_check( value => $self->{result_values}->{used}, threshold => [ { label => 'critical-' . $self->{result_values}->{crit_label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{result_values}->{warn_label}, exit_litteral => 'warning' } ] ); return $exit; } sub custom_usage_output { my ($self, %options) = @_; my $value = $self->{result_values}->{used} . ' files'; if ($self->{result_values}->{label_ref} eq 'data') { my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); $value = $total_used_value . " " . $total_used_unit; } my ($limit_soft, $limit_hard) = ('', ''); if (defined($self->{result_values}->{warn_limit}) && $self->{result_values}->{warn_limit} > 0) { $limit_soft = sprintf(" (%.2f %% of soft limit)", $self->{result_values}->{used} * 100 / $self->{result_values}->{warn_limit}); } if (defined($self->{result_values}->{crit_limit}) && $self->{result_values}->{crit_limit} > 0) { $limit_hard = sprintf(" (%.2f %% of hard limit)", $self->{result_values}->{used} * 100 / $self->{result_values}->{crit_limit}); } return sprintf( "%s used: %s%s%s", ucfirst($self->{result_values}->{label_ref}), $value, $limit_soft, $limit_hard ); } sub custom_usage_calc { my ($self, %options) = @_; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; $self->{result_values}->{label_ref} = $options{extra_options}->{label_ref}; $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label_ref} . '_used'}; $self->{result_values}->{warn_label} = $self->{label}; if (defined($self->{instance_mode}->{option_results}->{'warning-' . $self->{label}}) && $self->{instance_mode}->{option_results}->{'warning-' . $self->{label}} ne '') { $self->{result_values}->{warn_limit} = $self->{instance_mode}->{option_results}->{'warning-' . $self->{label}}; } elsif ($options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label_ref} . '_soft'} > 0) { $self->{result_values}->{warn_limit} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label_ref} . '_soft'}; $self->{perfdata}->threshold_validate(label => 'warning-' . $self->{label} . '_' . $self->{result_values}->{display}, value => $self->{result_values}->{warn_limit}); $self->{result_values}->{warn_label} = $self->{label} . '_' . $self->{result_values}->{display}; } $self->{result_values}->{crit_label} = $self->{label}; if (defined($self->{instance_mode}->{option_results}->{'critical-' . $self->{label}}) && $self->{instance_mode}->{option_results}->{'critical-' . $self->{label}} ne '') { $self->{result_values}->{crit_limit} = $self->{instance_mode}->{option_results}->{'critical-' . $self->{label}}; } elsif ($options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label_ref} . '_hard'} > 0) { $self->{result_values}->{crit_limit} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label_ref} . '_hard'} - 1; $self->{perfdata}->threshold_validate(label => 'critical-' . $self->{label} . '_' . $self->{result_values}->{display}, value => $self->{result_values}->{crit_limit}); $self->{result_values}->{crit_label} = $self->{label} . '_' . $self->{result_values}->{display}; } return 0; } sub prefix_quota_output { my ($self, %options) = @_; return "Quota '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'quota', type => 1, cb_prefix_output => 'prefix_quota_output', message_multiple => 'All quotas are ok' } ]; $self->{maps_counters}->{quota} = [ { label => 'data-usage', nlabel => 'quota.data.usage.bytes', set => { key_values => [ { name => 'display' }, { name => 'data_used' }, { name => 'data_soft' }, { name => 'data_hard' } ], closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'data' }, closure_custom_output => $self->can('custom_usage_output'), closure_custom_perfdata => $self->can('custom_usage_perfdata'), closure_custom_threshold_check => $self->can('custom_usage_threshold') } }, { label => 'inode-usage', nlabel => 'quota.files.usage.count', set => { key_values => [ { name => 'display' }, { name => 'inode_used' }, { name => 'inode_soft' }, { name => 'inode_hard' } ], closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'inode' }, closure_custom_output => $self->can('custom_usage_output'), closure_custom_perfdata => $self->can('custom_usage_perfdata'), closure_custom_threshold_check => $self->can('custom_usage_threshold') } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-user:s' => { name => 'filter_user' }, 'filter-fs:s' => { name => 'filter_fs' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout, $exit_code) = $options{custom}->execute_command( command => 'repquota', command_options => '-a -i 2>&1', no_quit => 1 ); #*** Report for user quotas on device /dev/xxxx #Block grace time: 7days; Inode grace time: 7days # Block limits File limits #User used soft hard grace used soft hard grace #---------------------------------------------------------------------- #root -- 20779412 0 0 5 0 0 #apache -- 5721908 0 0 67076 0 0 $self->{quota} = {}; while ($stdout =~ /^\*\*\*.*?(\S+?)\n(.*?)(?=\*\*\*|\z)/msig) { my ($fs, $data) = ($1, $2); while ($data =~ /^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.*?)\n/msig) { my ($user, $grace_on, $data_used, $data_soft, $data_hard, $usage) = ($1, $2, $3 * 1024, $4 * 1024, $5 * 1024, $6); my @values = split /\s+/, $usage; shift @values if ($usage =~ /^\+/); my ($inode_used, $inode_soft, $inode_hard) = (shift @values, shift @values, shift @values); my $name = $user . '.' . $fs; if (defined($self->{option_results}->{filter_user}) && $self->{option_results}->{filter_user} ne '' && $user !~ /$self->{option_results}->{filter_user}/) { $self->{output}->output_add(long_msg => "skipping '" . $name . "': no matching filter.", debug => 1); next; } if (defined($self->{option_results}->{filter_fs}) && $self->{option_results}->{filter_fs} ne '' && $fs !~ /$self->{option_results}->{filter_fs}/) { $self->{output}->output_add(long_msg => "skipping '" . $name . "': no matching filter.", debug => 1); next; } next if (defined($self->{option_results}->{exclude_fs}) && $self->{option_results}->{exclude_fs} ne '' && $fs =~ /$self->{option_results}->{exclude_fs}/); $self->{quota}->{$name} = { display => $name, data_used => $data_used, data_soft => $data_soft, data_hard => $data_hard, inode_used => $inode_used, inode_soft => $inode_soft, inode_hard => $inode_hard, }; } } if (scalar(keys %{$self->{quota}}) <= 0) { $self->{output}->add_option_msg(short_msg => "No quota found (filters or command issue)"); $self->{output}->option_exit(); } } 1; =head1 MODE Check quota usage on partitions. Command used: repquota -a -i 2>&1 =over 8 =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'inode-usage', 'data-usage'. =item B<--filter-user> Filter username (regexp can be used). =item B<--filter-fs> Filter filesystem (regexp can be used). =item B<--exclude-fs> Exclude filesystem (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_QUOTA $fatpacked{"os/linux/local/mode/resources/discovery.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_RESOURCES_DISCOVERY'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::resources::discovery; use strict; use warnings; use Exporter; our $discovery_match; our @ISA = qw(Exporter); our @EXPORT_OK = qw($discovery_match); $discovery_match = [ { type => 'cisco asa', re => qr/Cisco Adaptative Security Appliance/i }, { type => 'cisco standard', re => qr/Cisco IOS Software/i }, { type => 'emc data domain', re => qr/Data Domain/i }, { type => 'sonicwall', re => qr/SonicWALL/i }, { type => 'silverpeak', re => qr/Silver Peak/i }, { type => 'stonesoft', re => qr/Forcepoint/i }, { type => 'redback', re => qr/Redback/i }, { type => 'palo alto', re => qr/Palo Alto/i }, { type => 'hp procurve', re => qr/HP.*Switch/i }, { type => 'hp procurve', re => qr/HP ProCurve/i }, { type => 'hp standard', re => qr/HPE Comware/i }, { type => 'hp msl', re => qr/HP MSL/i }, { type => 'mrv optiswitch', re => qr/OptiSwitch/i }, { type => 'netapp', re => qr/Netapp/i }, { type => 'linux', re => qr/linux/i }, { type => 'windows', re => qr/windows/i }, { type => 'macos', re => qr/Darwin/i }, { type => 'hp-ux', re => qr/HP-UX/i }, { type => 'freebsd', re => qr/FreeBSD/i }, { type => 'aix', re => qr/ AIX / } ]; 1; OS_LINUX_LOCAL_MODE_RESOURCES_DISCOVERY $fatpacked{"os/linux/local/mode/storage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_STORAGE'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::storage; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub custom_usage_perfdata { my ($self, %options) = @_; my $label = $self->{nlabel}; my $value_perf = $self->{result_values}->{used}; if (defined($self->{instance_mode}->{option_results}->{free})) { $label = 'storage.space.free.bytes'; $value_perf = $self->{result_values}->{free}; } my %total_options = (); if ($self->{instance_mode}->{option_results}->{units} eq '%') { $total_options{total} = $self->{result_values}->{total}; $total_options{cast_int} = 1; } $self->{output}->perfdata_add( nlabel => $label, unit => 'B', instances => $self->{result_values}->{display}, value => $value_perf, warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}, %total_options), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}, %total_options), min => 0, max => $self->{result_values}->{total} ); } sub custom_usage_threshold { my ($self, %options) = @_; my ($exit, $threshold_value); $threshold_value = $self->{result_values}->{used}; $threshold_value = $self->{result_values}->{free} if (defined($self->{instance_mode}->{option_results}->{free})); if ($self->{instance_mode}->{option_results}->{units} eq '%') { $threshold_value = $self->{result_values}->{prct_used}; $threshold_value = $self->{result_values}->{prct_free} if (defined($self->{instance_mode}->{option_results}->{free})); } $exit = $self->{perfdata}->threshold_check(value => $threshold_value, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' } ]); return $exit; } sub custom_usage_output { my ($self, %options) = @_; my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); return sprintf( 'Usage Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)', $total_size_value . " " . $total_size_unit, $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free} ); } sub custom_usage_calc { my ($self, %options) = @_; if ($options{new_datas}->{$self->{instance} . '_total'} == 0) { $self->{error_msg} = "total size is 0"; return -2; } $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_total'}; $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_used'}; $self->{result_values}->{free} = $options{new_datas}->{$self->{instance} . '_free'}; $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / ($self->{result_values}->{used} + $self->{result_values}->{free}); $self->{result_values}->{prct_free} = 100 - $self->{result_values}->{prct_used}; return 0; } sub prefix_disks_output { my ($self, %options) = @_; return "Storage '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'disks', type => 1, cb_prefix_output => 'prefix_disks_output', message_multiple => 'All storages are ok' } ]; $self->{maps_counters}->{disks} = [ { label => 'usage', nlabel => 'storage.space.usage.bytes', set => { key_values => [ { name => 'display' }, { name => 'used' }, { name => 'free' }, { name => 'total' } ], closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_output => $self->can('custom_usage_output'), closure_custom_perfdata => $self->can('custom_usage_perfdata'), closure_custom_threshold_check => $self->can('custom_usage_threshold') } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-type:s' => { name => 'filter_type' }, 'filter-fs:s' => { name => 'filter_fs' }, 'exclude-fs:s' => { name => 'exclude_fs' }, 'filter-mountpoint:s' => { name => 'filter_mountpoint' }, 'exclude-mountpoint:s' => { name => 'exclude_mountpoint' }, 'units:s' => { name => 'units', default => '%' }, 'free' => { name => 'free' } }); return $self; } sub manage_selection { my ($self, %options) = @_; my ($stdout, $exit_code) = $options{custom}->execute_command( command => 'df', command_options => '-P -k -T 2>&1', no_quit => 1 ); $self->{disks} = {}; my @lines = split /\n/, $stdout; foreach my $line (@lines) { next if ($line !~ /^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/); my ($fs, $type, $used, $available, $percent, $mount) = ($1, $2, $4, $5, $6, $7); next if (defined($self->{option_results}->{filter_fs}) && $self->{option_results}->{filter_fs} ne '' && $fs !~ /$self->{option_results}->{filter_fs}/); next if (defined($self->{option_results}->{exclude_fs}) && $self->{option_results}->{exclude_fs} ne '' && $fs =~ /$self->{option_results}->{exclude_fs}/); next if (defined($self->{option_results}->{filter_type}) && $self->{option_results}->{filter_type} ne '' && $type !~ /$self->{option_results}->{filter_type}/); next if (defined($self->{option_results}->{filter_mountpoint}) && $self->{option_results}->{filter_mountpoint} ne '' && $mount !~ /$self->{option_results}->{filter_mountpoint}/); next if (defined($self->{option_results}->{exclude_mountpoint}) && $self->{option_results}->{exclude_mountpoint} ne '' && $mount =~ /$self->{option_results}->{exclude_mountpoint}/); $self->{disks}->{$mount} = { display => $mount, fs => $fs, type => $type, used => $used * 1024, free => $available * 1024, total => ($used + $available) * 1024 }; } if (scalar(keys %{$self->{disks}}) <= 0) { if ($exit_code != 0) { $self->{output}->output_add(long_msg => "command output:" . $stdout); } $self->{output}->add_option_msg(short_msg => "No storage found (filters or command issue)"); $self->{output}->option_exit(); } } 1; =head1 MODE Check storage usages. Command used: df -P -k -T 2>&1 =over 8 =item B<--warning-usage> Warning threshold. =item B<--critical-usage> Critical threshold. =item B<--units> Units of thresholds (default: '%') ('%', 'B'). =item B<--free> Thresholds are on free space left. =item B<--filter-mountpoint> Filter filesystem mount point (regexp can be used). =item B<--exclude-mountpoint> Exclude filesystem mount point (regexp can be used). =item B<--filter-type> Filter filesystem type (regexp can be used). =item B<--filter-fs> Filter filesystem (regexp can be used). =item B<--exclude-fs> Exclude filesystem (regexp can be used). =back =cut OS_LINUX_LOCAL_MODE_STORAGE $fatpacked{"os/linux/local/mode/swap.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_SWAP'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::swap; use base qw(centreon::plugins::templates::counter); use strict; use warnings; sub custom_swap_output { my ($self, %options) = @_; return sprintf( 'Swap Total: %s %s Used: %s %s (%.2f%%) Free: %s %s (%.2f%%)', $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}), $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}), $self->{result_values}->{prct_used}, $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}), $self->{result_values}->{prct_free} ); } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'swap', type => 0, skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{swap} = [ { label => 'usage', nlabel => 'swap.usage.bytes', set => { key_values => [ { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_swap_output'), perfdatas => [ { label => 'used', template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1 } ] } }, { label => 'usage-free', display_ok => 0, nlabel => 'swap.free.bytes', set => { key_values => [ { name => 'free' }, { name => 'used' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], closure_custom_output => $self->can('custom_swap_output'), perfdatas => [ { label => 'free', template => '%d', min => 0, max => 'total', unit => 'B', cast_int => 1 } ] } }, { label => 'usage-prct', display_ok => 0, nlabel => 'swap.usage.percentage', set => { key_values => [ { name => 'prct_used' } ], output_template => 'Swap used: %.2f %%', perfdatas => [ { label => 'used_prct', template => '%.2f', min => 0, max => 100, unit => '%' } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'no-swap:s' => { name => 'no_swap' } }); $self->{no_swap} = 'critical'; return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); if (defined($self->{option_results}->{no_swap}) && $self->{option_results}->{no_swap} ne '') { if ($self->{output}->is_litteral_status(status => $self->{option_results}->{no_swap}) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong --no-swap status '" . $self->{option_results}->{no_swap} . "'."); $self->{output}->option_exit(); } $self->{no_swap} = $self->{option_results}->{no_swap}; } } sub manage_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/meminfo 2>&1' ); my ($total_size, $swap_free); foreach (split(/\n/, $stdout)) { if (/^SwapTotal:\s+(\d+)/i) { $total_size = $1 * 1024; } elsif (/^SwapFree:\s+(\d+)/i) { $swap_free = $1 * 1024; } } if (!defined($total_size) || !defined($swap_free)) { $self->{output}->add_option_msg(short_msg => "Some information missing."); $self->{output}->option_exit(); } if ($total_size == 0) { $self->{output}->output_add( severity => $self->{no_swap}, short_msg => 'No active swap.' ); $self->{output}->display(); $self->{output}->exit(); } my $swap_used = $total_size - $swap_free; my $prct_used = $swap_used * 100 / $total_size; my $prct_free = 100 - $prct_used; my ($total_value, $total_unit) = $self->{perfdata}->change_bytes(value => $total_size); my ($swap_used_value, $swap_used_unit) = $self->{perfdata}->change_bytes(value => $swap_used); my ($swap_free_value, $swap_free_unit) = $self->{perfdata}->change_bytes(value => ($total_size - $swap_used)); $self->{swap} = { used => $swap_used, free => $swap_free, prct_used => $prct_used, prct_free => $prct_free, total => $total_size }; } 1; =head1 MODE Check swap memory (need '/proc/meminfo' file). Command used: cat /proc/meminfo 2>&1 =over 8 =item B<--no-swap> Threshold if no active swap (default: 'critical'). =item B<--warning-*> B<--critical-*> Threshold, can be 'usage' (in Bytes), 'usage-free' (in Bytes), 'usage-prct' (%). =back =cut OS_LINUX_LOCAL_MODE_SWAP $fatpacked{"os/linux/local/mode/systemdjournal.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_SYSTEMDJOURNAL'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::systemdjournal; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use DateTime; use Digest::MD5 qw(md5_hex); use JSON::XS; sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0 } ]; $self->{maps_counters}->{global} = [ { label => 'entries', nlabel => 'journal.entries.count', set => { key_values => [ { name => 'entries' } ], output_template => 'Journal entries: %s', perfdatas => [ { template => '%s', min => 0 } ] } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'unit:s' => { name => 'unit' }, 'filter-message:s' => { name => 'filter_message' }, 'since:s' => { name => 'since', default => 'cache' }, 'timezone:s' => { name => 'timezone', default => 'local' } }); return $self; } sub manage_selection { my ($self, %options) = @_; $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_message}) ? md5_hex($self->{option_results}->{filter_message}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{since}) ? md5_hex($self->{option_results}->{since}) : md5_hex('all')); my $command_options = '--output json --output-fields MESSAGE --no-pager'; if (defined($self->{option_results}->{unit}) && $self->{option_results}->{unit} ne '') { $command_options .= ' --unit ' . $self->{option_results}->{unit}; } if (defined($self->{option_results}->{since}) && $self->{option_results}->{since} ne '') { if ($self->{option_results}->{since} eq "cache") { my $last_timestamp = $self->read_statefile_key(key => 'last_timestamp'); $last_timestamp = time() - (5 * 60) if (!defined($last_timestamp)); my $dt = DateTime->from_epoch(epoch => $last_timestamp); $dt->set_time_zone($self->{option_results}->{timezone}); $command_options .= ' --since "' . $dt->ymd . ' ' . $dt->hms . '"'; } elsif ($self->{option_results}->{since} =~ /\d+/) { $command_options .= ' --since "' . $self->{option_results}->{since} . ' minutes ago"'; } } my ($stdout) = $options{custom}->execute_command( command => 'journalctl', command_options => $command_options . ' 2>&1' ); $self->{global} = { entries => 0 }; my @lines = split /\n/, $stdout; foreach (@lines) { my $decoded; eval { $decoded = JSON::XS->new->utf8->decode($_); }; if ($@) { $self->{output}->add_option_msg(short_msg => "Cannot decode json response"); $self->{output}->option_exit(); } next if (defined($self->{option_results}->{filter_message}) && $self->{option_results}->{filter_message} ne '' && $decoded->{MESSAGE} !~ /$self->{option_results}->{filter_message}/); $self->{global}->{entries}++; } } 1; =head1 MODE Count journal entries. Command used: journalctl --output json --output-fields MESSAGE --no-pager Examples: Look for sent emails by Postfix: # perl centreon_plugins.pl --plugin=os::linux::local::plugin --mode=systemd-journal --unit=postfix.service --filter-message='status=sent' --since=10 --change-short-output='Journal entries~Emails sent' --change-perfdata='journal.entries.count,emails.sent.count' OK: Emails sent: 17 | 'emails.sent.count'=17;;;0; Look for Puppet errors: # perl centreon_plugins.pl --plugin=os::linux::local::plugin --mode=systemd-journal --unit=puppet.service --filter-message='error' --since=30 OK: Journal entries: 1 | 'journal.entries.count'=1;;;0; Look for the number of Centreon Engine reloads # perl centreon_plugins.pl --plugin=os::linux::local::plugin --mode=systemd-journal --unit=centengine.service --filter-message='Reloaded.*Engine' --since=60 --change-short-output='Journal entries~Centreon Engine reloads over the last hour' --change-perfdata='journal.entries.count,centreon.engine.reload.count' OK: Centreon Engine reloads over the last hour: 0 | 'centreon.engine.reload.count'=0;;;0; =over 8 =item B<--unit> Only look for messages of the specified unit, ie the name of the systemd service who created the message. =item B<--filter-message> Filter on message content (can be a regexp). =item B<--since> Defines the amount of time to look back at messages. Can be minutes (ie 5 "minutes ago") or 'cache' to use the timestamp from last execution. (default: 'cache') =item B<--timezone> Defines the timezone to convert date/time to the host timezone when using timestamp from cache. (default: 'local') =item B<--warning-entries> B<--critical-entries> Thresholds on the number of entries found in the journal for the specified parameters. =back =cut OS_LINUX_LOCAL_MODE_SYSTEMDJOURNAL $fatpacked{"os/linux/local/mode/systemdscstatus.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_SYSTEMDSCSTATUS'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::systemdscstatus; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::misc; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); sub custom_status_output { my ($self, %options) = @_; return sprintf( 'status : %s/%s/%s [boot: %s]', $self->{result_values}->{load}, $self->{result_values}->{active}, $self->{result_values}->{sub}, $self->{result_values}->{boot} ); } sub prefix_sc_output { my ($self, %options) = @_; return "Service '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'global', type => 0 }, { name => 'sc', type => 1, cb_prefix_output => 'prefix_sc_output', message_multiple => 'All services are ok' } ]; $self->{maps_counters}->{global} = [ { label => 'total-running', nlabel => 'systemd.services.running.count', set => { key_values => [ { name => 'running' }, { name => 'total' } ], output_template => 'Total Running: %s', perfdatas => [ { label => 'total_running', template => '%s', min => 0, max => 'total' } ] } }, { label => 'total-failed', nlabel => 'systemd.services.failed.count', set => { key_values => [ { name => 'failed' }, { name => 'total' } ], output_template => 'Total Failed: %s', perfdatas => [ { label => 'total_failed', template => '%s', min => 0, max => 'total' } ] } }, { label => 'total-dead', nlabel => 'systemd.services.dead.count', set => { key_values => [ { name => 'dead' }, { name => 'total' } ], output_template => 'Total Dead: %s', perfdatas => [ { label => 'total_dead', template => '%s', min => 0, max => 'total' } ] } }, { label => 'total-exited', nlabel => 'systemd.services.exited.count', set => { key_values => [ { name => 'exited' }, { name => 'total' } ], output_template => 'Total Exited: %s', perfdatas => [ { label => 'total_exited', template => '%s', min => 0, max => 'total' } ] } } ]; $self->{maps_counters}->{sc} = [ { label => 'status', type => 2, critical_default => '%{active} =~ /failed/i', set => { key_values => [ { name => 'load' }, { name => 'active' }, { name => 'sub' }, { name => 'boot' }, { name => 'display' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $options{options}->add_options(arguments => { 'filter-name:s' => { name => 'filter_name' }, 'exclude-name:s' => { name => 'exclude_name' } }); return $self; } sub manage_selection { my ($self, %options) = @_; # check systemctl version to convert no-legend in legend=false (change in versions >= 248) my $legend_format= ' --no-legend'; my ($stdout_version) = $options{custom}->execute_command( command => 'systemctl', command_options => '--version' ); $stdout_version =~ /^systemd\s(\d+)\s/; my $systemctl_version=$1; if($systemctl_version >= 248){ $legend_format = ' --legend=false'; } my $command_options_1 = '-a --no-pager --plain'; my ($stdout) = $options{custom}->execute_command( command => 'systemctl', command_options => $command_options_1.$legend_format ); $self->{global} = { running => 0, exited => 0, failed => 0, dead => 0, total => 0 }; $self->{sc} = {}; #auditd.service loaded active running Security Auditing Service #avahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stack #brandbot.service loaded inactive dead Flexible Branding Service while ($stdout =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/msig) { my ($name, $load, $active, $sub) = ($1, $2, $3, lc($4)); next if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && $name !~ /$self->{option_results}->{filter_name}/); next if (defined($self->{option_results}->{exclude_name}) && $self->{option_results}->{exclude_name} ne '' && $name =~ /$self->{option_results}->{exclude_name}/); $self->{sc}->{$name} = { display => $name, load => $load, active => $active, sub => $sub, boot => '-' }; $self->{global}->{$sub} += 1 if (defined($self->{global}->{$sub})); $self->{global}->{total} += 1; } if (scalar(keys %{$self->{sc}}) <= 0) { $self->{output}->add_option_msg(short_msg => "No service found."); $self->{output}->option_exit(); } my $command_options_2 = 'list-unit-files --no-pager --plain'; my ($stdout_2) = $options{custom}->execute_command( command => 'systemctl', command_options => $command_options_2.$legend_format ); # vendor preset is a new column #UNIT FILE STATE VENDOR PRESET #runlevel4.target enabled #runlevel5.target static #runlevel6.target disabled #irqbalance.service enabled enabled while ($stdout_2 =~ /^(.*?)\s+(\S+)\s*/msig) { my ($name, $boot) = ($1, $2); next if (!defined($self->{sc}->{$name})); $self->{sc}->{$name}->{boot} = $boot; } } 1; =head1 MODE Check systemd services status. Command used: 'systemctl -a --no-pager --no-legend' and 'systemctl list-unit-files --no-pager --no-legend' Command change for systemctl version >= 248 : --no-legend is converted in legend=false =over 8 =item B<--filter-name> Filter service name (can be a regexp). =item B<--exclude-name> Exclude service name (can be a regexp). =item B<--warning-*> B<--critical-*> Thresholds. Can be: 'total-running', 'total-dead', 'total-exited', 'total-failed'. =item B<--warning-status> Define the conditions to match for the status to be WARNING. You can use the following variables: %{display}, %{active}, %{sub}, %{load}, %{boot} Example of statuses for the majority of these variables: %{active}: active, inactive %{sub}: waiting, plugged, mounted, dead, failed, running, exited, listening, active %{load}: loaded, not-found %{boot}: enabled, disabled, static, indirect =item B<--critical-status> Define the conditions to match for the status to be CRITICAL (default: '%{active} =~ /failed/i'). You can use the following variables: %{display}, %{active}, %{sub}, %{load}, %{boot} Example of statuses for the majority of these variables: %{active}: active, inactive %{sub}: waiting, plugged, mounted, dead, failed, running, exited, listening, active %{load}: loaded, not-found %{boot}: enabled, disabled, static, indirect =back =cut OS_LINUX_LOCAL_MODE_SYSTEMDSCSTATUS $fatpacked{"os/linux/local/mode/traffic.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_TRAFFIC'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::traffic; use base qw(centreon::plugins::templates::counter); use strict; use warnings; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); use Digest::MD5 qw(md5_hex); sub custom_status_output { my ($self, %options) = @_; return sprintf('status : %s', $self->{result_values}->{status}); } sub custom_traffic_perfdata { my ($self, %options) = @_; my ($warning, $critical); if ($self->{instance_mode}->{option_results}->{units} eq '%' && defined($self->{result_values}->{speed})) { $warning = $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}, total => $self->{result_values}->{speed}, cast_int => 1); $critical = $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}, total => $self->{result_values}->{speed}, cast_int => 1); } elsif ($self->{instance_mode}->{option_results}->{units} eq 'b/s') { $warning = $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}); $critical = $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}); } $self->{output}->perfdata_add( label => $self->{nlabel}, unit => 'b/s', instances => $self->{result_values}->{display}, value => sprintf("%.2f", $self->{result_values}->{traffic_per_seconds}), warning => $warning, critical => $critical, min => 0, max => $self->{result_values}->{speed} ); } sub custom_traffic_threshold { my ($self, %options) = @_; my $exit = 'ok'; if ($self->{instance_mode}->{option_results}->{units} eq '%' && defined($self->{result_values}->{speed})) { $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{traffic_prct}, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } ]); } elsif ($self->{instance_mode}->{option_results}->{units} eq 'b/s') { $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{traffic_per_seconds}, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } ]); } return $exit; } sub custom_traffic_output { my ($self, %options) = @_; my ($traffic_value, $traffic_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{traffic_per_seconds}, network => 1); return sprintf( 'Traffic %s : %s/s (%s)', ucfirst($self->{result_values}->{label}), $traffic_value . $traffic_unit, defined($self->{result_values}->{traffic_prct}) ? sprintf("%.2f%%", $self->{result_values}->{traffic_prct}) : '-' ); } sub custom_traffic_calc { my ($self, %options) = @_; my $diff_traffic = ($options{new_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref}} - $options{old_datas}->{$self->{instance} . '_' . $options{extra_options}->{label_ref}}); $self->{result_values}->{traffic_per_seconds} = $diff_traffic / $options{delta_time}; if (defined($options{new_datas}->{$self->{instance} . '_speed_' . $options{extra_options}->{label_ref}}) && $options{new_datas}->{$self->{instance} . '_speed_' . $options{extra_options}->{label_ref}} ne '' && $options{new_datas}->{$self->{instance} . '_speed_' . $options{extra_options}->{label_ref}} > 0) { $self->{result_values}->{traffic_prct} = $self->{result_values}->{traffic_per_seconds} * 100 / $options{new_datas}->{$self->{instance} . '_speed_' . $options{extra_options}->{label_ref}}; $self->{result_values}->{speed} = $options{new_datas}->{$self->{instance} . '_speed_' . $options{extra_options}->{label_ref}}; } $self->{result_values}->{label} = $options{extra_options}->{label_ref}; $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; return 0; } sub prefix_interface_output { my ($self, %options) = @_; return "Interface '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ { name => 'interface', type => 1, cb_prefix_output => 'prefix_interface_output', message_multiple => 'All interfaces are ok', skipped_code => { -10 => 1 } } ]; $self->{maps_counters}->{interface} = [ { label => 'status', type => 2, critical_default => '%{status} ne "RU"', set => { key_values => [ { name => 'status' }, { name => 'display' } ], closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } }, { label => 'in', nlabel => 'interface.traffic.in.bitspersecond', set => { key_values => [ { name => 'in', diff => 1 }, { name => 'speed_in' }, { name => 'display' } ], closure_custom_calc => $self->can('custom_traffic_calc'), closure_custom_calc_extra_options => { label_ref => 'in' }, closure_custom_output => $self->can('custom_traffic_output'), output_error_template => 'Traffic In : %s', closure_custom_perfdata => $self->can('custom_traffic_perfdata'), closure_custom_threshold_check => $self->can('custom_traffic_threshold') } }, { label => 'out', nlabel => 'interface.traffic.out.bitspersecond', set => { key_values => [ { name => 'out', diff => 1 }, { name => 'speed_out' }, { name => 'display' } ], closure_custom_calc => $self->can('custom_traffic_calc'), closure_custom_calc_extra_options => { label_ref => 'out' }, closure_custom_output => $self->can('custom_traffic_output'), output_error_template => 'Traffic Out : %s', closure_custom_perfdata => $self->can('custom_traffic_perfdata'), closure_custom_threshold_check => $self->can('custom_traffic_threshold') } } ]; } sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'filter-state:s' => { name => 'filter_state', }, 'filter-interface:s' => { name => 'filter_interface' }, 'exclude-interface:s' => { name => 'exclude_interface' }, 'units:s' => { name => 'units', default => 'b/s' }, 'speed:s' => { name => 'speed' }, 'guess-speed' => { name => 'guess_speed' }, 'no-loopback' => { name => 'no_loopback' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); if (defined($self->{option_results}->{speed}) && $self->{option_results}->{speed} ne '') { if ($self->{option_results}->{speed} !~ /^[0-9]+(\.[0-9]+){0,1}$/) { $self->{output}->add_option_msg(short_msg => "Speed must be a positive number '" . $self->{option_results}->{speed} . "' (can be a float also)."); $self->{output}->option_exit(); } else { $self->{option_results}->{speed} *= 1000000; } } if (defined($self->{option_results}->{units}) && $self->{option_results}->{units} eq '%' && (!defined($self->{option_results}->{speed}) || $self->{option_results}->{speed} eq '')) { $self->{output}->add_option_msg(short_msg => "To use percent, you need to set --speed option."); $self->{output}->option_exit(); } } sub guess_speed { my ($self, %options) = @_; if ($self->{iwconfig_output} =~ /^$options{name}\s+IEEE.*?Bit\s+Rate=([0-9\.]+)\s+Mb/ms) { return $1 * 1000000; } my ($output) = $options{custom}->execute_command( command => 'ethtool ' . $options{name}, no_quit => 1 ); if ($output =~ /Speed:\s+(.*)Mb/) { return $1 * 1000000; } return undef; } sub do_selection { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'ip', command_path => '/sbin', command_options => '-s addr 2>&1' ); if (defined($self->{option_results}->{guess_speed})) { ($self->{iwconfig_output}) = $options{custom}->execute_command( command => 'iwconfig', no_quit => 1 ); } # ifconfig my $interface_pattern = '^(\S+)(.*?)(\n\n|\n$)'; if ($stdout =~ /^\d+:\s+\S+:\s+</ms) { # ip addr $interface_pattern = '^\d+:\s+(\S+)(.*?)(?=\n\d|\Z$)'; } $self->{interface} = {}; while ($stdout =~ /$interface_pattern/msg) { my ($interface_name, $values) = ($1, $2); $interface_name =~ s/:$//; my $states = ''; $states .= 'R' if ($values =~ /RUNNING|LOWER_UP/ms); $states .= 'U' if ($values =~ /UP/ms); next if (defined($self->{option_results}->{no_loopback}) && $values =~ /LOOPBACK/ms); next if (defined($self->{option_results}->{filter_state}) && $self->{option_results}->{filter_state} ne '' && $states !~ /$self->{option_results}->{filter_state}/); next if (defined($self->{option_results}->{filter_interface}) && $self->{option_results}->{filter_interface} ne '' && $interface_name !~ /$self->{option_results}->{filter_interface}/); next if (defined($self->{option_results}->{exclude_interface}) && $self->{option_results}->{exclude_interface} ne '' && $interface_name =~ /$self->{option_results}->{exclude_interface}/); $self->{interface}->{$interface_name} = { display => $interface_name, status => $states, speed_in => '', speed_out => '' }; if (defined($self->{option_results}->{guess_speed})) { my $speed = $self->guess_speed(custom => $options{custom}, name => $interface_name); if (defined($speed)) { $self->{interface}->{$interface_name}->{speed_in} = $speed; $self->{interface}->{$interface_name}->{speed_out} = $speed; } } if (defined($self->{option_results}->{speed}) && $self->{option_results}->{speed} ne '') { $self->{interface}->{$interface_name}->{speed_in} = $self->{option_results}->{speed}; $self->{interface}->{$interface_name}->{speed_out} = $self->{option_results}->{speed}; }; # ip addr patterns if ($values =~ /RX:\s+bytes.*?(\d+).*?TX:\s+bytes.*?(\d+)/msi) { $self->{interface}->{$interface_name}->{in} = $1; $self->{interface}->{$interface_name}->{out} = $2; } elsif ($values =~ /RX bytes:(\S+).*?TX bytes:(\S+)/msi || $values =~ /RX packets\s+\d+\s+bytes\s+(\S+).*?TX packets\s+\d+\s+bytes\s+(\S+)/msi) { $self->{interface}->{$interface_name}->{in} = $1; $self->{interface}->{$interface_name}->{out} = $2; } } if (scalar(keys %{$self->{interface}}) <= 0) { $self->{output}->add_option_msg(short_msg => "No interface found."); $self->{output}->option_exit(); } } sub manage_selection { my ($self, %options) = @_; $self->do_selection(custom => $options{custom}); $self->{cache_name} = 'cache_linux_local_' . $options{custom}->get_identifier() . '_' . $self->{mode} . '_' . (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . (defined($self->{option_results}->{filter_interface}) ? md5_hex($self->{option_results}->{filter_interface}) : md5_hex('all')); } 1; =head1 MODE Check traffic Command used: /sbin/ip -s addr 2>&1 =over 8 =item B<--warning-in> Warning threshold in percent for 'in' traffic. =item B<--critical-in> Critical threshold in percent for 'in' traffic. =item B<--warning-out> Warning threshold in percent for 'out' traffic. =item B<--critical-out> Critical threshold in percent for 'out' traffic. =item B<--unknown-status> Define the conditions to match for the status to be UNKNOWN (default: ''). You can use the following variables: %{status}, %{display} =item B<--warning-status> Define the conditions to match for the status to be WARNING (default: ''). You can use the following variables: %{status}, %{display} =item B<--critical-status> Define the conditions to match for the status to be CRITICAL (default: '%{status} ne "RU"'). You can use the following variables: %{status}, %{display} =item B<--units> Units of thresholds (default: 'b/s') ('%', 'b/s'). Percent can be used only if --speed is set. =item B<--filter-interface> Filter interface name (regexp can be used). =item B<--exclude-interface> Exclude interface name (regexp can be used). =item B<--filter-state> Filter interfaces type (regexp can be used). =item B<--speed> Set interface speed (in Mb). =item B<--guess-speed> Try to guess speed with commands ethtool and iwconfig. =item B<--no-loopback> Don't display loopback interfaces. =back =cut OS_LINUX_LOCAL_MODE_TRAFFIC $fatpacked{"os/linux/local/mode/uptime.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_MODE_UPTIME'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::mode::uptime; use base qw(centreon::plugins::mode); use strict; use warnings; use POSIX; sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { 'warning:s' => { name => 'warning', default => '' }, 'critical:s' => { name => 'critical', default => '' }, 'seconds' => { name => 'seconds' } }); return $self; } sub check_options { my ($self, %options) = @_; $self->SUPER::init(%options); if (($self->{perfdata}->threshold_validate(label => 'warning', value => $self->{option_results}->{warning})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $self->{option_results}->{warning} . "'."); $self->{output}->option_exit(); } if (($self->{perfdata}->threshold_validate(label => 'critical', value => $self->{option_results}->{critical})) == 0) { $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $self->{option_results}->{critical} . "'."); $self->{output}->option_exit(); } } sub run { my ($self, %options) = @_; my ($stdout) = $options{custom}->execute_command( command => 'cat', command_options => '/proc/uptime 2>&1' ); my ($uptime, $idletime); if ($stdout =~ /^([0-9\.]+)\s+([0-9\.]+)/m) { ($uptime, $idletime) = ($1, $2) } if (!defined($uptime) || !defined($idletime)) { $self->{output}->add_option_msg(short_msg => 'Some informations missing.'); $self->{output}->option_exit(); } my $exit_code = $self->{perfdata}->threshold_check( value => floor($uptime), threshold => [ { label => 'critical', exit_litteral => 'critical' }, { label => 'warning', exit_litteral => 'warning' } ] ); $self->{output}->perfdata_add( nlabel => 'system.uptime.seconds', unit => 's', value => floor($uptime), warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning'), critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical'), min => 0 ); $self->{output}->output_add( severity => $exit_code, short_msg => sprintf( "System uptime is: %s", defined($self->{option_results}->{seconds}) ? floor($uptime) . " seconds" : floor($uptime / 86400) . " days" ) ); $self->{output}->display(); $self->{output}->exit(); } 1; =head1 MODE Check system uptime. Command used: cat /proc/uptime 2>&1 =over 8 =item B<--warning> Warning threshold in seconds. =item B<--critical> Critical threshold in seconds. =item B<--seconds> Display uptime in seconds. =back =cut OS_LINUX_LOCAL_MODE_UPTIME $fatpacked{"os/linux/local/plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OS_LINUX_LOCAL_PLUGIN'; # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package os::linux::local::plugin; use strict; use warnings; use base qw(centreon::plugins::script_custom); sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; $self->{modes} = { 'check-plugin' => 'os::linux::local::mode::checkplugin', 'cpu' => 'os::linux::local::mode::cpu', 'cpu-detailed' => 'os::linux::local::mode::cpudetailed', 'cmd-return' => 'os::linux::local::mode::cmdreturn', 'connections' => 'os::linux::local::mode::connections', 'discovery-snmp' => 'os::linux::local::mode::discoverysnmp', 'discovery-snmpv3' => 'os::linux::local::mode::discoverysnmpv3', 'diskio' => 'os::linux::local::mode::diskio', 'files-size' => 'os::linux::local::mode::filessize', 'files-date' => 'os::linux::local::mode::filesdate', 'inodes' => 'os::linux::local::mode::inodes', 'load' => 'os::linux::local::mode::loadaverage', 'lvm' => 'os::linux::local::mode::lvm', 'list-interfaces' => 'os::linux::local::mode::listinterfaces', 'list-partitions' => 'os::linux::local::mode::listpartitions', 'list-storages' => 'os::linux::local::mode::liststorages', 'list-systemdservices' => 'os::linux::local::mode::listsystemdservices', 'memory' => 'os::linux::local::mode::memory', 'mountpoint' => 'os::linux::local::mode::mountpoint', 'open-files' => 'os::linux::local::mode::openfiles', 'ntp' => 'os::linux::local::mode::ntp', 'packet-errors' => 'os::linux::local::mode::packeterrors', 'paging' => 'os::linux::local::mode::paging', 'pending-updates' => 'os::linux::local::mode::pendingupdates', 'process' => 'os::linux::local::mode::process', 'quota' => 'os::linux::local::mode::quota', 'storage' => 'os::linux::local::mode::storage', 'swap' => 'os::linux::local::mode::swap', 'systemd-journal' => 'os::linux::local::mode::systemdjournal', 'systemd-sc-status' => 'os::linux::local::mode::systemdscstatus', 'traffic' => 'os::linux::local::mode::traffic', 'uptime' => 'os::linux::local::mode::uptime' }; $self->{custom_modes}->{cli} = 'os::linux::local::custom::cli'; return $self; } 1; =head1 PLUGIN DESCRIPTION Check Linux through local commands (the plugin can use SSH). =cut OS_LINUX_LOCAL_PLUGIN s/^ //mg for values %fatpacked; my $class = 'FatPacked::'.(0+\%fatpacked); no strict 'refs'; *{"${class}::files"} = sub { keys %{$_[0]} }; if ($] < 5.008) { *{"${class}::INC"} = sub { if (my $fat = $_[0]{$_[1]}) { my $pos = 0; my $last = length $fat; return (sub { return 0 if $pos == $last; my $next = (1 + index $fat, "\n", $pos) || $last; $_ .= substr $fat, $pos, $next - $pos; $pos = $next; return 1; }); } }; } else { *{"${class}::INC"} = sub { if (my $fat = $_[0]{$_[1]}) { open my $fh, '<', \$fat or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; return $fh; } return; }; } unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE # # Copyright 2024 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. use strict; use warnings; # Not perl embedded compliant at all use FindBin; use lib "$FindBin::Bin"; # use lib '/usr/lib/nagios/plugins/'; use centreon::plugins::script; centreon::plugins::script->new()->run();
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor