retire da-backup checks
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / dsa-check-libs
1 #!/usr/bin/perl -w
2
3 # Copyright (C) 2005, 2006, 2007, 2008, 2012, 2015 Peter Palfrader <peter@palfrader.org>
4 #               2012 Uli Martens <uli@youam.net>
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 use strict;
26 use English;
27 use Getopt::Long;
28
29 $ENV{'PATH'} = '/bin:/sbin:/usr/bin:/usr/sbin';
30 delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
31
32 my $LSOF = '/usr/bin/lsof -F0';
33 my $VERSION = '0.2015012901';
34
35 # nagios exit codes
36 my $OK = 0;
37 my $WARNING = 1;
38 my $CRITICAL = 2;
39 my $UNKNOWN = 3;
40
41 my $params;
42 my $config;
43
44 Getopt::Long::config('bundling');
45
46 sub dief {
47         print STDERR @_;
48         exit $UNKNOWN;
49 }
50
51 sub convert_time {
52         my $in = shift;
53         my ($ticks, $unit) = ($in =~ /^(\d+)([smhdw]?)$/);
54
55         if ($unit eq 's' || $unit eq '') { }
56         elsif ($unit eq 'm') { $ticks *= 60; }
57         elsif ($unit eq 'h') { $ticks *= 60*60; }
58         elsif ($unit eq 'd') { $ticks *= 60*60*24; }
59         elsif ($unit eq 'w') { $ticks *= 60*60*24*7; }
60         else { die "Invalid unit '$unit' in '$in'\n" }
61         return $ticks;
62 }
63
64
65 if (!GetOptions (
66         '--help'                        => \$params->{'help'},
67         '--version'                     => \$params->{'version'},
68         '--quiet'                       => \$params->{'quiet'},
69         '--ignore-pid-namespaces'       => \$params->{'ignore_pid_namespaces'},
70         '--verbose'                     => \$params->{'verbose'},
71         '--ignore-younger=s'            => \$params->{'ignore_younger'},
72         '-v'                            => \$params->{'verbose'},
73         '--config=s'                    => \$params->{'config'},
74         )) {
75         dief ("$PROGRAM_NAME: Usage: $PROGRAM_NAME [--help|--version] [--ignore-pid-namespaces] [--verbose] [--quiet] [--config=<CONFIGFILE>]\n");
76 };
77 if ($params->{'help'}) {
78         print "$PROGRAM_NAME: Usage: $PROGRAM_NAME [--help|--version] [--ignore-pid-namespaces] [--verbose] [--quiet] [--config=<CONFIGFILE>]\n";
79         print "Reports processes that are linked against libraries that no longer exist.\n";
80         print "The optional config file can specify ignore rules - see the sample config file.\n";
81         print "Can optionally ignore processes from other PID namespaces.\n";
82         exit (0);
83 };
84 if ($params->{'version'}) {
85         print "nagios-check-libs $VERSION\n";
86         print "nagios check for availability of debian (security) updates\n";
87         print "Copyright (c) 2005, 2006, 2007, 2008, 2012 Peter Palfrader <peter\@palfrader.org>\n";
88         exit (0);
89 };
90
91 if (! defined $params->{'config'}) {
92         $params->{'config'} = '/etc/nagios/check-libs.conf';
93 } elsif (! -e $params->{'config'}) {
94         dief("Config file $params->{'config'} does not exist.\n");
95 }
96
97 if (-e $params->{'config'}) {
98         eval "use YAML::Syck; 1" or dief "you need YAML::Syck (libyaml-syck-perl) to load a config file";
99         open(my $fh, '<', $params->{'config'}) or dief "Cannot open config file $params->{'config'}: $!";
100         $config = LoadFile($fh);
101         close($fh);
102         if (!(ref($config) eq "HASH")) {
103                 dief("Loaded config is not a hash!\n");
104         }
105 } else {
106         $config = {
107                 'ignorelist' => [
108                         '$path =~ m#^/proc/#',
109                         '$path =~ m#^/var/tmp/#',
110                         '$path =~ m#^/SYS#',
111                         '$path =~ m#^/drm$# # xserver stuff',
112                         '$path =~ m#^/dev/zero#',
113                         '$path =~ m#^/dev/shm/#',
114                 ]
115         };
116 }
117
118 if (! exists $config->{'ignorelist'}) {
119         $config->{'ignorelist'} = [];
120 } elsif (! (ref($config->{'ignorelist'}) eq 'ARRAY')) {
121         dief("Config->ignorelist is not an array!\n");
122 }
123
124 my %processes;
125
126 sub getPIDs($$) {
127         my ($user, $process) = @_;
128         return join(', ', sort keys %{ $processes{$user}->{$process} });
129 };
130 sub getProcs($) {
131         my ($user) = @_;
132
133         return join(', ', map { $_.' ('.getPIDs($user, $_).')' } (sort {$a cmp $b} keys %{ $processes{$user} }));
134 };
135 sub getUsers() {
136         return join('; ', (map { $_.': '.getProcs($_) } (sort {$a cmp $b} keys %processes)));
137 };
138 sub inVserver() {
139         my ($f, $key);
140         if (-e "/proc/self/vinfo" ) {
141                 $f = "/proc/self/vinfo";
142                 $key = "XID";
143         } else {
144                 $f = "/proc/self/status";
145                 $key = "s_context";
146         };
147         open(F, "< $f") or return 0;
148         while (<F>) {
149                 my ($k, $v) = split(/: */, $_, 2);
150                 if ($k eq $key) {
151                         close F;
152                         return ($v > 0);
153                 };
154         };
155         close F;
156         return 0;
157 }
158 sub getYoungProcesses() {
159         my $ignore_hash = {};
160         if (defined $params->{'ignore_younger'}) {
161                 eval "use Proc::ProcessTable; 1" or die "We need Proc::ProcessTable to use --ignore-younger.\n";
162                 my $min_age = convert_time($params->{'ignore_younger'});
163                 my $cutoff = time() - $min_age;
164
165                 my $process_table = new Proc::ProcessTable(enable_ttys => 0);
166                 for my $p ( @{$process_table->table} ){
167                         $ignore_hash->{$p->pid} = 1 if $p->start > $cutoff;
168                 }
169         }
170         return $ignore_hash;
171 }
172
173
174 my $IGNORE_YOUNG_PROCESSES = getYoungProcesses();
175
176
177
178
179 my $INVSERVER = inVserver();
180
181 my $PID_NS_SUPPORT = (-f '/proc/self/ns/pid');
182 my $PID_NS;
183 if ($PID_NS_SUPPORT and $params->{'ignore_pid_namespaces'}) {
184         $PID_NS = readlink('/proc/self/ns/pid');
185 }
186
187 print STDERR "Running $LSOF -n\n" if $params->{'verbose'};
188 open (LSOF, "$LSOF -n 2>/dev/null|") or dief ("Cannot run $LSOF -n: $!\n");
189 my @lsof=<LSOF>;
190 close LSOF;
191 if ($CHILD_ERROR) { # program failed
192         dief("$LSOF -n returned with non-zero exit code: ".($CHILD_ERROR / 256)."\n");
193 };
194
195 my ($process, $pid, $user);
196 LINE: for my $line (@lsof)  {
197         if ( $line =~ /^p/ ) {
198                 my %fields = map { m/^(.)(.*)$/ ; $1 => $2 } grep { defined $_  and length $_ >1} split /\0/, $line;
199                 $process = $fields{c};
200                 $pid     = $fields{p};
201                 $user    = $fields{L} || '<unknown>';
202                 next;
203         }
204
205         if ($PID_NS_SUPPORT and $params->{'ignore_pid_namespaces'}) {
206                 my $pidns = readlink('/proc/'.$pid.'/ns/pid');
207                 next if (defined $pidns and $PID_NS ne $pidns);
208         }
209
210         unless ( $line =~ /^f/ ) {
211                 dief("UNKNOWN strange line read from lsof\n");
212                 # don't print it because it contains NULL characters...
213         }
214
215         my %fields = map { m/^(.)(.*)$/ ; $1 => $2 } grep { defined $_  and length $_ >1} split /\0/, $line;
216
217         my $fd    = $fields{f};
218         my $inode = $fields{i};
219         my $path  = $fields{n};
220         if ($path =~ m/\.dpkg-/ || $path =~ m/\(deleted\)/ || $path =~ /path inode=/ || $path =~ m#/\.nfs# || $fd eq 'DEL') {
221                 my $deleted_in_path = ($path =~ m/\(deleted\)/ || $path =~ m/\.nfs/);
222                 next if ($deleted_in_path && $fd =~ /^[0-9]*$/); # Ignore deleted files that are open via normal file handles.
223                 next if ($deleted_in_path && $fd eq 'cwd'); # Ignore deleted directories that we happen to be in.
224
225                 $path =~ s/^\(deleted\)//; # in some cases "(deleted)" is at the beginning of the string
226                 for my $i (@{$config->{'ignorelist'}}) {
227                         my $ignore = eval($i);
228                         next LINE if $ignore;
229                 }
230                 next if ($INVSERVER && ($process eq 'init') && ($pid == 1) && ($user eq 'root'));
231                 if (exists $IGNORE_YOUNG_PROCESSES->{$pid}) {
232                         if ( $params->{'verbose'} ) {
233                                 print STDERR "ignoring young $process($pid) because of [$path]:\n";
234                                 print STDERR $line;
235                         }
236                         next LINE
237                 }
238                 if ( $params->{'verbose'} ) {
239                         print STDERR "adding $process($pid) because of [$path]:\n";
240                         print STDERR $line;
241                 }
242                 $processes{$user}->{$process}->{$pid} = 1;
243         };
244 };
245
246
247
248 my $message='';
249 my $exit = $OK;
250 if (keys %processes) {
251         $exit = $WARNING;
252         $message = 'The following processes have libs linked that were upgraded: '. getUsers()."\n";
253 } else {
254         $message = "No upgraded libs linked in running processes\n" unless $params->{'quiet'};
255 };
256
257 print $message;
258 exit $exit;