check
branchheiko
changeset 6 aafd8332d7a6
parent 3 71a0c4bd43b1
parent 5 52faa36ba7f8
child 7 2bda25a39ef4
equal deleted inserted replaced
3:71a0c4bd43b1 6:aafd8332d7a6
     1 #! /usr/bin/perl
     1 #! /usr/bin/perl
       
     2 # source: https://ssl.schlittermann.de/hg/ius/nagios/nagios-plugin-amanda-client
     2 
     3 
     3 use 5.010;
     4 use 5.010;
     4 use strict;
     5 use strict;
     5 use warnings;
     6 use warnings;
     6 use Getopt::Long;
     7 use Getopt::Long;
    26 
    27 
    27 sub ok;
    28 sub ok;
    28 sub warning;
    29 sub warning;
    29 sub critical;
    30 sub critical;
    30 sub unknown;
    31 sub unknown;
       
    32 sub verbose;
       
    33 sub unique { my %h; @h{@_} = (); keys %h }
    31 
    34 
    32 $SIG{__DIE__} = sub { unknown @_ unless $^S };
    35 $SIG{__DIE__} = sub { unknown @_ unless $^S };
    33 
    36 
    34 exit main @ARGV;
    37 exit main @ARGV;
    35 
    38 
    36 #----
    39 #----
    37 
    40 
    38 sub main {
    41 sub main {
    39 	my @opt_ignore;
    42     my @opt_ignore;
    40 
    43     my $opt_verbose = 0;
    41 	GetOptions(
    44 
    42 		'i|ignore=s@' => \@opt_ignore,
    45     GetOptions(
    43 		'h|help' => sub { pod2usage(-verbose => 1, -exit => 0) },
    46         'i|ignore=s@' => \@opt_ignore,
    44 		'm|man'  => sub { pod2usage(-verbose => 2, -exit => 0) },
    47         'h|help'      => sub { pod2usage(-verbose => 1, -exit => 0) },
    45 	) or pod2usage;
    48         'm|man'       => sub { pod2usage(-verbose => 2, -exit => 0) },
    46 
    49         'v|verbose'   => \$opt_verbose,
    47 	# test needs to be run as root:* or as backup:backup
    50     ) or pod2usage;
    48 	my $USER = 'backup';
    51 
    49 	my $CFDIR = '/etc/amanda';
    52     if ($opt_verbose) {
    50 
    53         *::verbose = sub { say '# ', @_ }
    51 	# change to backup if still root
    54     }
    52 	su $USER if $> == 0;
    55     else {
    53 
    56         *::verbose = sub { }
    54 	# amservice needs to be suid root, but executable
    57     }
    55 	# by the backup user/group
    58 
    56 	eval { check_perms find_tool('amservice'), 04750, 'root', $) }
    59     # test needs to be run as root:* or as backup:backup
    57 		or unknown $@;
    60     my $USER  = 'backup';
    58 
    61     my $CFDIR = '/etc/amanda';
    59 	# find the backup sets we know about
    62 
    60 	# here we suppose that it's possible to find strings like
    63     # change to backup if still root
    61 	# 'conf "foo"' in files named 'amanda-client.conf' below /etc/amanda
    64     su $USER if $> == 0;
    62 
    65 
    63 	my @confs = eval { config_names $CFDIR }
    66     # amservice needs to be suid root, but executable
    64 		or unknown $@;
    67     # by the backup user/group
    65 
    68     verbose q{checking permissions for `amservice'};
    66 	eval { amchecks @confs } or critical $@;
    69     eval { check_perms find_tool('amservice'), 04750, 'root', $) }
    67 
    70       or unknown $@;
    68 	my @dles = eval { amlists @confs } or critical $@;
    71 
    69 	ok @dles;
    72     # find the backup sets we know about
    70 
    73     # here we suppose that it's possible to find strings like
    71 	# never reached
    74     # 'conf "foo"' in files named 'amanda-client.conf' below /etc/amanda
    72 	return 0;
    75 
       
    76     verbose qq{find config names from $CFDIR};
       
    77     my @confs = sort +unique eval { config_names $CFDIR }
       
    78       or unknown $@;
       
    79 
       
    80     eval { amchecks @confs } or critical $@;
       
    81 
       
    82     my @dles = eval { amlists @confs } or critical $@;
       
    83     ok 'config: ' . join(', ', @confs), @dles;
       
    84 
       
    85     # never reached
       
    86     return 0;
    73 }
    87 }
    74 
    88 
    75 # compare the file systems
    89 # compare the file systems
    76 # get a list of file system
    90 # get a list of file system
    77 sub get_devices {
    91 sub get_devices {
    78 	open(my $fh, '/proc/filesystems');
    92     open(my $fh, '/proc/filesystems');
    79 	my @types = map { /^\s+(\S+)/ ? $1 : () } <$fh>;
    93     my @types = map { /^\s+(\S+)/ ? $1 : () } <$fh>;
    80 	my @df = (df => '-P', map { -t => $_ } @types);
    94     my @df = (df => '-P', map { -t => $_ } @types);
    81 	map { [$_, (stat)[0]] } map { (split ' ', $_)[5] } grep { /^\// } `@df`;
    95     map { [$_, (stat)[0]] } map { (split ' ', $_)[5] } grep { /^\// } `@df`;
    82 }
    96 }
    83 
    97 
    84 sub su {
    98 sub su {
    85 	my $user = shift;
    99     my $user  = shift;
    86 	my $group = (getgrnam $user)[0];
   100     my $group = (getgrnam $user)[0];
    87 	my $uid = getpwnam $user;
   101     my $uid   = getpwnam $user;
    88 	my $gid = getgrnam $group;
   102     my $gid   = getgrnam $group;
    89 
   103 
    90 	my @groups;
   104     my @groups;
    91 	
   105 
    92 	setgrent;
   106     setgrent;
    93 	my @rc;
   107     my @rc;
    94 	while (my @g = getgrent) {
   108     while (my @g = getgrent) {
    95 		 push @groups, $g[2] if $user ~~ [split ' ', $g[3]];
   109         push @groups, $g[2] if $user ~~ [split ' ', $g[3]];
    96 	}
   110     }
    97 	endgrent;
   111     endgrent;
    98 	$) = "@groups";
   112     $) = "@groups";
    99 	setgid $gid;
   113 
   100 	setuid $uid;
   114     verbose "su to $uid:$gid";
       
   115     setgid $gid;
       
   116     setuid $uid;
   101 }
   117 }
   102 
   118 
   103 sub find_tool {
   119 sub find_tool {
   104 	my $name = shift;
   120     my $name = shift;
   105 	my @rc = grep { -f -x } map { catfile $_, $name } split /:/, $ENV{PATH}
   121     my @rc = grep { -f -x } map { catfile $_, $name } split /:/, $ENV{PATH}
   106 		or die "Can't find `$name' in $ENV{PATH}\n";
   122       or die "Can't find `$name' in $ENV{PATH}\n";
   107 	$rc[0];
   123     $rc[0];
   108 };
   124 }
   109 
   125 
   110 sub check_perms {
   126 sub check_perms {
   111 	my ($file, $mode, $owner, $group) = @_;
   127     my ($file, $mode, $owner, $group) = @_;
   112 
   128 
   113 	$owner = getpwuid $owner if $owner ~~ /^\d+$/;
   129     $owner = getpwuid $owner if $owner ~~ /^\d+$/;
   114 	
   130 
   115 	$group = getgrgid +(split ' ', $group)[0]
   131     $group = getgrgid +(split ' ', $group)[0]
   116 		if $group ~~ /^[\d\s]+$/;
   132       if $group ~~ /^[\d\s]+$/;
   117 
   133 
   118 	stat $file or croak "Can't stat `$file': $!\n";
   134     stat $file or croak "Can't stat `$file': $!\n";
   119 
   135 
   120 	eval {
   136     eval {
   121 		my $f_owner = getpwuid +(stat _)[4] or die $!;
   137         my $f_owner = getpwuid +(stat _)[4] or die $!;
   122 		my $f_group = getgrgid +(stat _)[5] or die $!;
   138         my $f_group = getgrgid +(stat _)[5] or die $!;
   123 		my $f_mode = (stat _)[2] & 07777 or die $!;
   139         my $f_mode  = (stat _)[2] & 07777   or die $!;
   124 
   140 
   125 		my $msg = sprintf "need: 0%04o root:$group, got: 0%04o $f_owner:$f_group\n", 
   141         my $msg =
   126 			$mode, $f_mode;
   142           sprintf "need: 0%04o root:$group, got: 0%04o $f_owner:$f_group\n",
   127 
   143           $mode, $f_mode;
   128 		die $msg unless $f_owner eq $owner;
   144 
   129 		die $msg unless $f_group eq $group;
   145         die $msg unless $f_owner eq $owner;
   130 		die $msg unless $f_mode == $mode;
   146         die $msg unless $f_group eq $group;
   131 	};
   147         die $msg unless $f_mode == $mode;
   132 	die "wrong permissions for `$file', $@" if $@;
   148     };
   133 	1;
   149     die "wrong permissions for `$file', $@" if $@;
   134 }
   150     1;
   135 
   151 }
   136 
   152 
   137 sub config_names {
   153 sub config_names {
   138 	my $dir = shift;
   154     my $dir     = shift;
   139 	my @configs = ();
   155     my @configs = ();
   140 	find(sub {
   156     find(
   141 		-f and /^amanda-client\.conf$/ or return;
   157         sub {
   142 		open(my $fh, '<', $_) or die "Can't open  $File::Find::name: $!\n";
   158             -f and /^amanda-client\.conf$/ or return;
   143 		push @configs, map { /^conf\s+"(.+?)"/ ? $1 : () } <$fh>;
   159             open(my $fh, '<', $_) or die "Can't open  $File::Find::name: $!\n";
   144 	}, $dir);
   160             push @configs, map { /^conf\s+"(.+?)"/ ? $1 : () } <$fh>;
   145 
   161         },
   146 	die "no configs found below $dir (amanda-client.conf needs need `conf \"foo\"' line)\n"
   162         $dir
   147 		if not @configs;
   163     );
   148 	return @configs;
   164 
   149 };
   165     die
       
   166 "no configs found below $dir (amanda-client.conf needs need `conf \"foo\"' line)\n"
       
   167       if not @configs;
       
   168     return @configs;
       
   169 }
   150 
   170 
   151 sub _amcheck {
   171 sub _amcheck {
   152 	#config: daily
   172 
   153 	#CHECKING
   173     #config: daily
   154 	#
   174     #CHECKING
   155 	#Amanda Backup Client Hosts Check
   175     #
   156 	#--------------------------------
   176     #Amanda Backup Client Hosts Check
   157 	#Client check: 1 host checked in 2.242 seconds.  0 problems found.
   177     #--------------------------------
   158 	#
   178     #Client check: 1 host checked in 2.242 seconds.  0 problems found.
   159 	#(brought to you by Amanda 3.3.1)
   179     #
   160 	#The check is finished
   180     #(brought to you by Amanda 3.3.1)
   161 
   181     #The check is finished
   162 	my $conf = shift;
   182 
   163 	my $_ = qx(amdump_client --config '$conf' check 2>&1);
   183     my $conf = shift;
   164 	/^config:\s+$conf\n
   184     my $_    = qx(amdump_client --config '$conf' check 2>&1);
       
   185     /^config:\s+$conf\n
   165 		 CHECKING\n
   186 		 CHECKING\n
   166 		 .*\n
   187 		 .*\n
   167 		 Client.check:.1.host.checked.in.\d+\.\d+.seconds\.\s+0.problems.found\.\n
   188 		 Client.check:.1.host.checked.in.\d+\.\d+.seconds\.\s+0.problems.found\.\n
   168 		 .*\n
   189 		 .*\n
   169 		 The.check.is.finished$
   190 		 The.check.is.finished$
   170 	/smx or die "unexpected output from check:\n$_";
   191 	/smx or die "unexpected output from check:\n$_";
   171 }
   192 }
   172 
   193 
   173 sub amchecks {
   194 sub amchecks {
   174 	my @errors = ();
   195     my @errors = ();
   175 	foreach my $conf (@_) {
   196     foreach my $conf (@_) {
   176 		eval { _amcheck $conf } or push @errors, $@;
   197         eval { _amcheck $conf } or push @errors, $@;
   177 	}
   198     }
   178 	die @errors if @errors;
   199     die @errors if @errors;
   179 	return 1;
   200     return 1;
   180 }
   201 }
   181 
   202 
   182 sub _amlist {
   203 sub _amlist {
   183 	# return a list of [ name, dev ] tupels.
   204 
   184 	# name: the name of the disk/device
   205     # return a list of [ name, dev ] tupels.
   185 	# dev:  the local device id (stat)[0]
   206     # name: the name of the disk/device
   186 	# iff the inum of $name != 2, it's not the top directory
   207     # dev:  the local device id (stat)[0]
   187 	# and we set the device id to -1, since $name does not stand for a whole
   208     # iff the inum of $name != 2, it's not the top directory
   188 	# device
   209     # and we set the device id to -1, since $name does not stand for a whole
   189 	my $conf = shift;
   210     # device
   190 	chomp((undef, my @dles) = qx(amdump_client --config '$conf' list));
   211     my $conf = shift;
   191 	return map { [$_, (stat $_)[1] == 2 ? (stat $_)[0] : -1 ] } @dles;
   212     chomp((undef, my @dles) = qx(amdump_client --config '$conf' list));
       
   213     return map { [$_, (stat $_)[1] == 2 ? (stat $_)[0] : -1] } @dles;
   192 }
   214 }
   193 
   215 
   194 sub amlists {
   216 sub amlists {
   195 	my @confs = @_;
   217     my @confs      = @_;
   196 	my @candidates = get_devices;
   218     my @candidates = get_devices;
   197 
   219 
   198 	my %missing;
   220     my %missing;
   199 
   221 
   200 	foreach my $conf (@confs) {
   222     foreach my $conf (@confs) {
   201 		my @dles = _amlist $conf;
   223         my @dles = _amlist $conf;
   202 		foreach my $candidate (@candidates) {
   224         foreach my $candidate (@candidates) {
   203 			# we're satisfied if either the name of the device is in
   225 
   204 			# the disklist, or the device id is found
   226             # we're satisfied if either the name of the device is in
   205 			$candidate->[0] ~~ [ map { $_->[0] } @dles ] and next;
   227             # the disklist, or the device id is found
   206 			$candidate->[1] ~~ [ map { $_->[1] } @dles ] and next;
   228             $candidate->[0] ~~ [map { $_->[0] } @dles] and next;
   207 			push @{$missing{$conf}}, $candidate->[0];
   229             $candidate->[1] ~~ [map { $_->[1] } @dles] and next;
   208 		}
   230             push @{ $missing{$conf} }, $candidate->[0];
   209 	}
   231         }
   210 	die map { "$_ missing: " . join(', ' => @{$missing{$_}}) . "\n" } keys %missing
   232     }
   211 		if %missing;
   233     die map { "$_ missing: " . join(', ' => @{ $missing{$_} }) . "\n" }
   212 	return map { $_->[0] } @candidates;
   234       keys %missing
       
   235       if %missing;
       
   236     return map { $_->[0] } @candidates;
   213 }
   237 }
   214 
   238 
   215 sub ok { say "$NAME OK\n", join "\n" => @_; exit 0 }
   239 sub ok { say "$NAME OK\n", join "\n" => @_; exit 0 }
   216 sub warning { print "$NAME WARNING\n", join "\n" => @_; exit 1 }
   240 sub warning  { print "$NAME WARNING\n",  join "\n" => @_; exit 1 }
   217 sub critical { print "$NAME CRITICAL\n", join "\n" => @_; exit 2 }
   241 sub critical { print "$NAME CRITICAL\n", join "\n" => @_; exit 2 }
   218 sub unknown { print "$NAME UNKNOWN\n", join "\n" => @_; exit 3 }
   242 sub unknown  { print "$NAME UNKNOWN\n",  join "\n" => @_; exit 3 }
   219 
   243 
   220 __END__
   244 __END__
   221 
   245 
   222 =head1 NAME
   246 =head1 NAME
   223 
   247 
   245 
   269 
   246 =item B<-m>|B<--man>
   270 =item B<-m>|B<--man>
   247 
   271 
   248 Show the man page of this tool.
   272 Show the man page of this tool.
   249 
   273 
       
   274 =item B<-v>|B<--verbose>
       
   275 
       
   276 Show what's going only. Many for debugging purpose. (default: off)
       
   277 
   250 =back
   278 =back
   251 
   279 
       
   280 =head1 PREPARATIONS
       
   281 
       
   282 In order to make the check working, some preparations needs to be done.
       
   283 
       
   284 =head1 Client
       
   285 
       
   286 For each backup set you want to check: Create an
       
   287 F</etc/amanda/$set/amanda-client.conf>. 
       
   288 
       
   289     config "foo"
       
   290     index-server    "amanda.example.com"    # used by restore
       
   291     tape-server	    "amanda.example.com"    # used by restore
       
   292     amdump-server   "amanda.example.com"    # used by amdump_client
       
   293 
       
   294 In addition, the F<amservice> binary has to be suid root and executable
       
   295 by the backup user. This requirement is checked automatically by the
       
   296 plugin.
       
   297 
       
   298 =head1 Server
       
   299 
       
   300 The server need to know about the amdumpd service. In the F<inetd.conf> 
       
   301 you need to add "amdumpd" to the list of allowed services. And
       
   302 additionally in F<.amandahosts> the "backup" user of the client needs
       
   303 the permissions to run the "amdumpd" service.
       
   304 
       
   305     # inetd.conf
       
   306     amanda stream tcp nowait backup /usr/lib/amanda/amandad amandad -auth=bsdtcp amindexd amidxtaped amdumpd
       
   307 
       
   308     # .amandahosts
       
   309     client.example.com backup amdumpd
       
   310     client.example.com root amindexd amidxtaped 
       
   311 
   252 =head1 AUTHOR
   312 =head1 AUTHOR
   253 
   313 
   254 Heiko Schlittermann L<hs@schlittermann.de>
   314 Heiko Schlittermann L<hs@schlittermann.de>
   255 
   315 
   256 =head1 SOURCE
   316 =head1 SOURCE
   257 
   317 
   258 Source can be found at L<https://ssl.schlittermann.de/hg/nagios/nagios-plugin-amanda-client>
   318 Source can be found at L<https://ssl.schlittermann.de/hg/nagios/nagios-plugin-amanda-client>
   259 
   319 
   260 =cut
   320 =cut
   261 
   321 
   262 
   322 # vim:et ts=4 sw=4 aw ai: