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 |