Add freeze, frozen_shell, hcoop-kprop.
[hcoop/scripts.git] / freeze
... / ...
CommitLineData
1#!/usr/bin/perl
2
3#
4# Purpose: freeze user (cancel user services except email), or unfreeze user.
5#
6# Usage (RUN AS _ADMIN USER ON DELEUZE WITHOUT SUDO):
7#
8# Display frozen users or details for one user (one user implies -verbose):
9# freeze [user], OR
10# freeze --action list [--verbose] | freeze -a l [-v | user]
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.
59# If you create a new module that will use the store (announce it's
60# hash key by creating it empty in record().
61#
62# For additional help, here's how the stored hash might look like:
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
89use warnings;
90use strict;
91
92use Storable qw/lock_nstore lock_retrieve/;
93use Getopt::Long qw/GetOptions/;
94
95use constant DEBUG => 1;
96use constant DRY => 1;
97use constant STORE => "/tmp/store";
98use constant DEFAULT_SHELL => '/bin/bash';
99use constant FROZEN_SHELL => '/afs/hcoop.net/common/etc/scripts/frozen_shell';
100use constant PUBLIC_ACCESS => [qw/mire/];
101use constant RUN_SERVER => 'handgun';
102
103my $store = {}; # cached info
104my $action = 'list'; # list, freeze, unfreeze
105my $verbose = 0; # 0/1 (print which modules were applied to user in freezing)
106my $force = 0; # 0/1 (force action even if user already freezed/unfreezed)
107my $user; # who to freeze/unfreeze
108my @user; # getent passwd entry for user
109my $modules; # list of freeze/unfreeze actions to take
110my @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
115unless ( 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
124if ( -e STORE ) {
125 $store = lock_retrieve(STORE);
126} else {
127 warn "No '" . STORE . "', skipping load.\n";
128}
129
130if ( `hostname` ne RUN_SERVER . "\n" ) {
131 die "Please run script on " . RUN_SERVER . "\n";
132}
133
134if ( $action =~ /^l/i ) {
135 while (my ($k,$v) = each %$store ) {
136 if (! $user or $user eq $k ) {
137 print "$k $$v{date}\n";
138 print " @{$$v{modules}}\n" if $verbose or $user;
139 }
140 }
141 exit 0;
142}
143
144$user or die "Must specify user to freeze/unfreeze. Exiting.\n";
145$user =~ /^[a-z0-9]+$/ or die "Invalid username (not [a-z0-9]+).\n";
146@user = split(/:/, `getent passwd $user`);
147@user or die "No such user? (getent passwd USER empty.)\n";
148chomp $user[$#user];
149
150if ( $action !~ /^[fu]/i ) {
151 warn "Unknown action: use -a [list (l)|freeze (f)|unfreeze (u)]\n";
152}
153
154if ( $action =~ /^f/i ) {
155 if ( exists $$store{$user} ) {
156 warn "User already frozen since $$store{$user}{date}.\n";
157 if (! $force) {
158 die "Exiting.\n";
159 }
160 }
161}
162
163elsif ( $action =~ /^u/i ) {
164 if (! exists $$store{$user} ) {
165 warn "User not frozen in the first place.\n";
166 if (! $force) {
167 die "Exiting.\n";
168 }
169 }
170}
171
172else {
173 warn "How did you get through?\n";
174 die;
175}
176
177
178if ($modules) { @modules = split /[,\s+]/, $modules; }
179for ( $action =~ /^u/i ? reverse @modules : @modules ) {
180 no strict 'refs';
181 print "Module: $_\n";
182 &{ $_ }($action, $user);
183}
184
185lock_nstore $store, STORE;
186
187
188###########################################################################
189# Helpers below
190
191# GETENT:
192# 0 1 2 3 4 5 6
193# docelic:x:10235:65534:docelic:/afs/hcoop.net/user/d/do/docelic:/bin/bash
194
195sub record {
196 my ($a, $u) = @_;
197 $a =~ /^f/i and $$store{$u} = {
198 date => scalar localtime,
199 getent => [ @user ],
200 modules => [],
201 domains => [],
202 cron => [],
203 };
204 $a =~ /^u/i and delete $$store{$u};
205}
206
207sub login {
208 my ($a, $u) = @_;
209
210 if ($a =~ /^f/i) {
211 if ( $user[6] ne DEFAULT_SHELL ) {
212 $$store{$u}{shell} = $user[6] unless $user[6] eq FROZEN_SHELL;
213 }
214
215
216 if ( -e "$user[5]/.loginshell" ) {
217 if (!DRY) {
218 unlink "$user[5]/.loginshell" or warn "unlink: $!"
219 } else {
220 warn qq{unlink $user[5]/.loginshell\n};
221 }
222 }
223
224 if (!DRY) {
225 symlink FROZEN_SHELL, "$user[5]/.loginshell"
226 or warn "symlink: $!";
227 } else {
228 warn qq{symlink FROZEN_SHELL, "$user[5]/.loginshell"\n}
229 }
230
231 push @{ $$store{$u}{modules} }, 'login';
232
233 if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
234 }
235
236 elsif ($a =~ /^u/i) {
237 if ( $$store{$u}{shell}) {
238 if ( -l "$user[5]/.loginshell" or -e "$user[5]/.loginshell" ) {
239 if (!DRY) {
240 system("rm '$user[5]/.loginshell'");
241 } else {
242 warn qq{system("rm '$user[5]/.loginshell'")\n};
243 }
244 }
245 if (!DRY) {
246 symlink($$store{$u}{shell}, "$user[5]/.loginshell")
247 or warn "symlink: $!";
248 } else {
249 warn qq|symlink($$store{$u}{shell}, "$user[5]/.loginshell")\n|;
250 }
251 }
252
253 @{ $$store{$u}{modules} } = grep {!/^login$/} @{ $$store{$u}{modules} };
254
255 if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
256 }
257}
258
259
260sub domtool {
261 my ($a, $u) = @_;
262
263 # XXX handle all types of domtool privs, not just domains
264 # XXX how to restart services after that?
265
266 if ($a =~ /^f/i) {
267 my $domains = `domtool-admin perms docelic | grep '^domain: '`;
268 chomp $domains;
269 my @domains = split / +/, $domains;
270
271 for (@domains) {
272 push @{ $$store{$u}{domains} }, $_;
273 if (!DRY) {
274 system("domtool-admin rmdom $_")
275 } else {
276 warn qq|system("domtool-admin rmdom $_")\n|
277 }
278 }
279
280 if (!DRY) {
281 system("domtool-rmuser $_")
282 } else {
283 warn qq|system("domtool-rmuser $_"\n|
284 }
285
286 push @{ $$store{$u}{modules} }, 'domtool';
287 }
288
289 elsif ($a =~ /^u/i) {
290 if (!DRY) {
291 system("domtool-adduser $_")
292 } else {
293 warn qq|system("domtool-adduser $_")\n|
294 }
295
296 for ( @{ $$store{$u}{domains} } ) {
297 if (!DRY) {
298 system("domtool-admin grant $u domain $_")
299 } else {
300 warn qq|system("domtool-admin grant $u domain $_")\n|
301 }
302 }
303
304 @{ $$store{$u}{modules} } = grep {!/^domtool$/} @{ $$store{$u}{modules} };
305 }
306}
307
308
309
310sub cron {
311 my ($a, $u) = @_;
312
313 if ($a =~ /^f/i) {
314 for ( PUBLIC_ACCESS ) {
315 if ( qx{ssh -K $_ grep -E '^$u\$' /etc/cron.allow }) {
316 push @{ $$store{$u}{cron} }, $_;
317
318 if (!DRY) {
319 qx{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow }
320 } else {
321 warn qq{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow\n}
322 }
323 }
324 }
325
326 push @{ $$store{$u}{modules} }, 'cron';
327 }
328
329 elsif ($a =~ /^u/i) {
330 for ( @{ $$store{$u}{cron} } ) {
331 if (!DRY) {
332 qx{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'};
333 } else {
334 warn qq{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'\n};
335 }
336 }
337
338 @{ $$store{$u}{modules} } = grep {!/^cron$/} @{ $$store{$u}{modules} };
339 }
340}
341
342
343sub slay {
344 my ($a, $u) = @_;
345
346 if ($a =~ /^f/i) {
347 for ( PUBLIC_ACCESS ) {
348 if (!DRY) {
349 qx{ssh -K $_ slay $u}; sleep 5; qx{ssh -K $_ slay -9 $u};
350 } else {
351 warn qq|ssh -K $_ slay $u; sleep 5; ssh -K $_ slay -9 $u\n|
352 }
353 }
354
355 push @{ $$store{$u}{modules} }, 'slay';
356 }
357
358 elsif ($a =~ /^f/i) {
359 @{ $$store{$u}{modules} } = grep {!/^slay$/} @{ $$store{$u}{modules} };
360 }
361}
362
363