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