Commit | Line | Data |
---|---|---|
86a0f2e2 | 1 | #!/usr/bin/perl |
2 | ||
3 | # | |
4 | # Purpose: freeze user (cancel user services except email), or unfreeze user. | |
5 | # | |
6d76f213 | 6 | # Usage (RUN AS _ADMIN USER ON GIBRAN WITHOUT SUDO ... but it shouldn't matter where you run it): |
86a0f2e2 | 7 | # |
8 | # Display frozen users or details for one user (one user implies -verbose): | |
9 | # freeze [user], OR | |
2639c68f | 10 | # freeze [ --action list | -a ] [--verbose | -v] [user] |
86a0f2e2 | 11 | # |
12 | # Freeze user: | |
13 | # freeze -a f user | |
14 | # | |
15 | # Unfreeze: | |
16 | # freeze -a u user | |
17 | # | |
18 | # | |
19 | # How it works: | |
20 | # | |
21 | # Script invokes a list of modules, where each module knows how to | |
22 | # implement and unimplement a specific restriction. Implemented modules | |
23 | # should be listed in @modules array or they won't get called. Admin | |
24 | # can override list of modules with --modules=one,two,three. | |
25 | # | |
26 | # Modules execute in the order as specified for freeze, and in reverse | |
27 | # order for unfreeze. | |
28 | # | |
29 | # Module gets called as &module($action, $user) . The proper way to | |
30 | # test for which action is requested is as shown below. (Note that | |
31 | # module adds or removes itself to the list of modules that ran on the user). | |
32 | # | |
33 | # if ($a =~ /^f/i) { | |
34 | # ... freeze ... | |
35 | # push @{ $$store{$u}{modules} }, 'MODULE'; | |
36 | # } elsif ($a =~ /^u/i) { | |
37 | # ... unfreeze ... | |
38 | # @{ $$store{$u}{modules} } = grep {!/^MODULE$/} @{ $$store{$u}{modules} }; | |
39 | # } | |
40 | # | |
41 | # Also each system-modifying action should be wrapped in if (!DRY) as shown: | |
42 | # | |
43 | # if (!DRY) { | |
44 | # system(qq{SOME COMMAND}) | |
45 | # } else { | |
46 | # warn qq|SOME COMMAND| | |
47 | # } | |
48 | # | |
49 | # User is valid system username, and user's getent entry is prepared and | |
50 | # retrievable through @user array, should you need some of its info. | |
51 | # | |
52 | # Module can save all persistent data to $$store{$user}{$modulename}. For | |
53 | # example, after cron module removes user from all cron.allows, it | |
54 | # registers the machines where user was removed to | |
55 | # @{ $$store{$u}{cron} }, so that it can revert it back if user is | |
56 | # unfreezed. | |
57 | # | |
58 | # Module 'record' creates or deletes initial user entry in $$store. | |
2639c68f | 59 | # If you create a new module that will use the store, announce its |
86a0f2e2 | 60 | # hash key by creating it empty in record(). |
61 | # | |
2639c68f | 62 | # For additional detail, here's how the stored hash might look like: |
86a0f2e2 | 63 | # |
64 | # | |
65 | # $store = { | |
66 | # user1 => { | |
67 | # date => 'Sun Jun 29 18:45:43 CEST 2008', | |
68 | # getent => [qw/docelic 1000 1000 DavorOcelic /home/docelic /bin/bash] | |
69 | # | |
70 | # modules => [qw/login domtool cron slay/], # (modules that ran) | |
71 | # domains => [qw/spinlock.hr test.hr/], # (domains that were removed) | |
72 | # cron => [qw/mire/], # (hosts where cron.allow entry was removed) | |
73 | # }, | |
74 | # user2 => { | |
75 | # ... | |
76 | # }, | |
77 | # ... | |
78 | # ... | |
79 | # ... | |
80 | # } | |
81 | # | |
82 | # | |
83 | # Wiki page relating to this script is http://wiki.hcoop.net/MemberFreezing | |
84 | # | |
85 | # Davor Ocelic, docelic@hcoop.net, Sun Jun 29 18:41:02 CEST 2008 | |
86 | # | |
87 | # | |
88 | ||
89 | use warnings; | |
90 | use strict; | |
91 | ||
92 | use Storable qw/lock_nstore lock_retrieve/; | |
93 | use Getopt::Long qw/GetOptions/; | |
94 | ||
95 | use constant DEBUG => 1; | |
2639c68f | 96 | use constant DRY => 0; |
4e8f181d | 97 | use constant STORE => "/afs/hcoop.net/common/etc/frozen/cache"; |
86a0f2e2 | 98 | use constant DEFAULT_SHELL => '/bin/bash'; |
99 | use constant FROZEN_SHELL => '/afs/hcoop.net/common/etc/scripts/frozen_shell'; | |
6d76f213 CE |
100 | use constant PUBLIC_ACCESS => (qw/marsh/); |
101 | use constant RUN_SERVER => 'gibran'; | |
86a0f2e2 | 102 | |
103 | my $store = {}; # cached info | |
104 | my $action = 'list'; # list, freeze, unfreeze | |
105 | my $verbose = 0; # 0/1 (print which modules were applied to user in freezing) | |
106 | my $force = 0; # 0/1 (force action even if user already freezed/unfreezed) | |
107 | my $user; # who to freeze/unfreeze | |
108 | my @user; # getent passwd entry for user | |
109 | my $modules; # list of freeze/unfreeze actions to take | |
110 | my @modules; # modules, possibly overriden by split /,\s+/, $modules | |
111 | ||
112 | # Keep modules listed in order of application, honoring possible dependencies. | |
113 | @modules = (qw/record login domtool slay/); | |
114 | ||
115 | unless ( GetOptions ( | |
116 | 'do|d|a=s' => \$action, | |
117 | 'verbose|v!' => \$verbose, | |
118 | 'force!' => \$force, | |
119 | 'modules|m=s' => \$modules, | |
120 | )) { die "Error parsing options: $!\n" } | |
121 | ||
122 | $user = shift ; | |
123 | ||
2639c68f | 124 | if ( $> == 0 or $< == 0 ) { |
125 | die "Run script under admin account without sudo.\n"; | |
126 | } | |
127 | ||
86a0f2e2 | 128 | if ( -e STORE ) { |
129 | $store = lock_retrieve(STORE); | |
130 | } else { | |
131 | warn "No '" . STORE . "', skipping load.\n"; | |
132 | } | |
133 | ||
134 | if ( `hostname` ne RUN_SERVER . "\n" ) { | |
135 | die "Please run script on " . RUN_SERVER . "\n"; | |
136 | } | |
137 | ||
138 | if ( $action =~ /^l/i ) { | |
139 | while (my ($k,$v) = each %$store ) { | |
140 | if (! $user or $user eq $k ) { | |
141 | print "$k $$v{date}\n"; | |
142 | print " @{$$v{modules}}\n" if $verbose or $user; | |
143 | } | |
144 | } | |
145 | exit 0; | |
146 | } | |
147 | ||
148 | $user or die "Must specify user to freeze/unfreeze. Exiting.\n"; | |
149 | $user =~ /^[a-z0-9]+$/ or die "Invalid username (not [a-z0-9]+).\n"; | |
150 | @user = split(/:/, `getent passwd $user`); | |
151 | @user or die "No such user? (getent passwd USER empty.)\n"; | |
152 | chomp $user[$#user]; | |
153 | ||
154 | if ( $action !~ /^[fu]/i ) { | |
155 | warn "Unknown action: use -a [list (l)|freeze (f)|unfreeze (u)]\n"; | |
156 | } | |
157 | ||
158 | if ( $action =~ /^f/i ) { | |
159 | if ( exists $$store{$user} ) { | |
160 | warn "User already frozen since $$store{$user}{date}.\n"; | |
161 | if (! $force) { | |
162 | die "Exiting.\n"; | |
163 | } | |
164 | } | |
165 | } | |
166 | ||
167 | elsif ( $action =~ /^u/i ) { | |
168 | if (! exists $$store{$user} ) { | |
169 | warn "User not frozen in the first place.\n"; | |
170 | if (! $force) { | |
171 | die "Exiting.\n"; | |
172 | } | |
173 | } | |
174 | } | |
175 | ||
176 | else { | |
177 | warn "How did you get through?\n"; | |
178 | die; | |
179 | } | |
180 | ||
181 | ||
182 | if ($modules) { @modules = split /[,\s+]/, $modules; } | |
183 | for ( $action =~ /^u/i ? reverse @modules : @modules ) { | |
184 | no strict 'refs'; | |
185 | print "Module: $_\n"; | |
186 | &{ $_ }($action, $user); | |
187 | } | |
188 | ||
189 | lock_nstore $store, STORE; | |
190 | ||
191 | ||
192 | ########################################################################### | |
193 | # Helpers below | |
194 | ||
2639c68f | 195 | # GETENT (available to modules automatically in @user): |
86a0f2e2 | 196 | # 0 1 2 3 4 5 6 |
197 | # docelic:x:10235:65534:docelic:/afs/hcoop.net/user/d/do/docelic:/bin/bash | |
198 | ||
199 | sub record { | |
200 | my ($a, $u) = @_; | |
201 | $a =~ /^f/i and $$store{$u} = { | |
202 | date => scalar localtime, | |
203 | getent => [ @user ], | |
204 | modules => [], | |
205 | domains => [], | |
206 | cron => [], | |
207 | }; | |
208 | $a =~ /^u/i and delete $$store{$u}; | |
209 | } | |
210 | ||
211 | sub login { | |
212 | my ($a, $u) = @_; | |
213 | ||
214 | if ($a =~ /^f/i) { | |
215 | if ( $user[6] ne DEFAULT_SHELL ) { | |
216 | $$store{$u}{shell} = $user[6] unless $user[6] eq FROZEN_SHELL; | |
217 | } | |
218 | ||
219 | ||
220 | if ( -e "$user[5]/.loginshell" ) { | |
221 | if (!DRY) { | |
222 | unlink "$user[5]/.loginshell" or warn "unlink: $!" | |
223 | } else { | |
224 | warn qq{unlink $user[5]/.loginshell\n}; | |
225 | } | |
226 | } | |
227 | ||
228 | if (!DRY) { | |
229 | symlink FROZEN_SHELL, "$user[5]/.loginshell" | |
230 | or warn "symlink: $!"; | |
231 | } else { | |
232 | warn qq{symlink FROZEN_SHELL, "$user[5]/.loginshell"\n} | |
233 | } | |
234 | ||
235 | push @{ $$store{$u}{modules} }, 'login'; | |
236 | ||
237 | if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") }; | |
238 | } | |
239 | ||
240 | elsif ($a =~ /^u/i) { | |
aeae8905 | 241 | if ( -l "$user[5]/.loginshell" or -e "$user[5]/.loginshell" ) { |
242 | if (!DRY) { | |
243 | system("rm '$user[5]/.loginshell'"); | |
244 | } else { | |
245 | warn qq{system("rm '$user[5]/.loginshell'")\n}; | |
246 | } | |
247 | } | |
248 | ||
86a0f2e2 | 249 | if ( $$store{$u}{shell}) { |
86a0f2e2 | 250 | if (!DRY) { |
251 | symlink($$store{$u}{shell}, "$user[5]/.loginshell") | |
252 | or warn "symlink: $!"; | |
253 | } else { | |
254 | warn qq|symlink($$store{$u}{shell}, "$user[5]/.loginshell")\n|; | |
255 | } | |
256 | } | |
257 | ||
258 | @{ $$store{$u}{modules} } = grep {!/^login$/} @{ $$store{$u}{modules} }; | |
259 | ||
260 | if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") }; | |
261 | } | |
262 | } | |
263 | ||
264 | ||
265 | sub domtool { | |
266 | my ($a, $u) = @_; | |
267 | ||
268 | # XXX handle all types of domtool privs, not just domains | |
269 | # XXX how to restart services after that? | |
270 | ||
271 | if ($a =~ /^f/i) { | |
2639c68f | 272 | my $domains = `domtool-admin perms $u | grep '^domain: '`; |
86a0f2e2 | 273 | chomp $domains; |
274 | my @domains = split / +/, $domains; | |
275 | ||
276 | for (@domains) { | |
277 | push @{ $$store{$u}{domains} }, $_; | |
e3f713a3 | 278 | |
279 | # As per adamc's suggestion, I should not be | |
280 | # running rmdom explicitly. | |
281 | # https://bugzilla.hcoop.net/show_bug.cgi?id=555 | |
c78d8f9c CE |
282 | # adam was wrong, rmuser is too broad. rmdom + revoke |
283 | if (!DRY) { | |
284 | system("domtool-admin rmdom $_"); | |
285 | system("domtool-admin revoke $u domain $_"); | |
286 | } else { | |
287 | warn qq|system("domtool-admin rmdom $_")\n|; | |
288 | warn qq|system("domtool-admin revoke $u domain $_")\n| | |
289 | } | |
d5a2aada | 290 | } |
86a0f2e2 | 291 | |
292 | push @{ $$store{$u}{modules} }, 'domtool'; | |
293 | } | |
294 | ||
295 | elsif ($a =~ /^u/i) { | |
d5a2aada | 296 | if (!DRY) { |
c78d8f9c | 297 | system("domtool-adduser $u") |
d5a2aada | 298 | } else { |
c78d8f9c | 299 | warn qq|system("domtool-adduser $u")\n| |
d5a2aada | 300 | } |
86a0f2e2 | 301 | |
302 | for ( @{ $$store{$u}{domains} } ) { | |
303 | if (!DRY) { | |
304 | system("domtool-admin grant $u domain $_") | |
305 | } else { | |
306 | warn qq|system("domtool-admin grant $u domain $_")\n| | |
307 | } | |
308 | } | |
309 | ||
310 | @{ $$store{$u}{modules} } = grep {!/^domtool$/} @{ $$store{$u}{modules} }; | |
311 | } | |
312 | } | |
313 | ||
314 | ||
315 | ||
316 | sub cron { | |
317 | my ($a, $u) = @_; | |
318 | ||
319 | if ($a =~ /^f/i) { | |
320 | for ( PUBLIC_ACCESS ) { | |
321 | if ( qx{ssh -K $_ grep -E '^$u\$' /etc/cron.allow }) { | |
322 | push @{ $$store{$u}{cron} }, $_; | |
323 | ||
324 | if (!DRY) { | |
325 | qx{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow } | |
326 | } else { | |
327 | warn qq{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow\n} | |
328 | } | |
329 | } | |
330 | } | |
331 | ||
332 | push @{ $$store{$u}{modules} }, 'cron'; | |
333 | } | |
334 | ||
335 | elsif ($a =~ /^u/i) { | |
336 | for ( @{ $$store{$u}{cron} } ) { | |
337 | if (!DRY) { | |
338 | qx{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'}; | |
339 | } else { | |
340 | warn qq{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'\n}; | |
341 | } | |
342 | } | |
343 | ||
344 | @{ $$store{$u}{modules} } = grep {!/^cron$/} @{ $$store{$u}{modules} }; | |
345 | } | |
346 | } | |
347 | ||
348 | ||
349 | sub slay { | |
350 | my ($a, $u) = @_; | |
351 | ||
352 | if ($a =~ /^f/i) { | |
353 | for ( PUBLIC_ACCESS ) { | |
354 | if (!DRY) { | |
2639c68f | 355 | qx{ssh -K $_ sudo slay $u}; sleep 5; qx{ssh -K $_ sudo slay -9 $u}; |
86a0f2e2 | 356 | } else { |
2639c68f | 357 | warn qq|ssh -K $_ sudo slay $u; sleep 5; ssh -K $_ sudo slay -9 $u\n| |
86a0f2e2 | 358 | } |
359 | } | |
360 | ||
361 | push @{ $$store{$u}{modules} }, 'slay'; | |
362 | } | |
363 | ||
364 | elsif ($a =~ /^f/i) { | |
365 | @{ $$store{$u}{modules} } = grep {!/^slay$/} @{ $$store{$u}{modules} }; | |
366 | } | |
367 | } | |
368 | ||
369 |