4 # Purpose: freeze user (cancel user services except email), or unfreeze user.
6 # Usage (RUN AS _ADMIN USER ON DELEUZE WITHOUT SUDO):
8 # Display frozen users or details for one user (one user implies -verbose):
10 # freeze --action list [--verbose] | freeze -a l [-v | user]
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.
26 # Modules execute in the order as specified for freeze, and in reverse
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).
35 # push @{ $$store{$u}{modules} }, 'MODULE';
36 # } elsif ($a =~ /^u/i) {
38 # @{ $$store{$u}{modules} } = grep {!/^MODULE$/} @{ $$store{$u}{modules} };
41 # Also each system-modifying action should be wrapped in if (!DRY) as shown:
44 # system(qq{SOME COMMAND})
46 # warn qq|SOME COMMAND|
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.
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
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().
62 # For additional help, here's how the stored hash might look like:
67 # date => 'Sun Jun 29 18:45:43 CEST 2008',
68 # getent => [qw/docelic 1000 1000 DavorOcelic /home/docelic /bin/bash]
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)
83 # Wiki page relating to this script is http://wiki.hcoop.net/MemberFreezing
85 # Davor Ocelic, docelic@hcoop.net, Sun Jun 29 18:41:02 CEST 2008
92 use Storable qw
/lock_nstore lock_retrieve/;
93 use Getopt
::Long qw
/GetOptions/;
95 use constant DEBUG
=> 1;
96 use constant DRY
=> 1;
97 use constant STORE
=> "/tmp/store";
98 use constant DEFAULT_SHELL
=> '/bin/bash';
99 use constant FROZEN_SHELL
=> '/afs/hcoop.net/common/etc/scripts/frozen_shell';
100 use constant PUBLIC_ACCESS
=> [qw
/mire/];
101 use constant RUN_SERVER
=> 'handgun';
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
112 # Keep modules listed in order of application, honoring possible dependencies.
113 @modules = (qw
/record login domtool slay/);
115 unless ( GetOptions
(
116 'do|d|a=s' => \
$action,
117 'verbose|v!' => \
$verbose,
119 'modules|m=s' => \
$modules,
120 )) { die "Error parsing options: $!\n" }
125 $store = lock_retrieve
(STORE
);
127 warn "No '" . STORE
. "', skipping load.\n";
130 if ( `hostname` ne RUN_SERVER
. "\n" ) {
131 die "Please run script on " . RUN_SERVER
. "\n";
134 if ( $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;
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";
150 if ( $action !~ /^[fu]/i ) {
151 warn "Unknown action: use -a [list (l)|freeze (f)|unfreeze (u)]\n";
154 if ( $action =~ /^f/i ) {
155 if ( exists $$store{$user} ) {
156 warn "User already frozen since $$store{$user}{date}.\n";
163 elsif ( $action =~ /^u/i ) {
164 if (! exists $$store{$user} ) {
165 warn "User not frozen in the first place.\n";
173 warn "How did you get through?\n";
178 if ($modules) { @modules = split /[,\s+]/, $modules; }
179 for ( $action =~ /^u/i ?
reverse @modules : @modules ) {
181 print "Module: $_\n";
182 &{ $_ }($action, $user);
185 lock_nstore
$store, STORE
;
188 ###########################################################################
193 # docelic:x:10235:65534:docelic:/afs/hcoop.net/user/d/do/docelic:/bin/bash
197 $a =~ /^f/i and $$store{$u} = {
198 date
=> scalar localtime,
204 $a =~ /^u/i and delete $$store{$u};
211 if ( $user[6] ne DEFAULT_SHELL
) {
212 $$store{$u}{shell
} = $user[6] unless $user[6] eq FROZEN_SHELL
;
216 if ( -e
"$user[5]/.loginshell" ) {
218 unlink "$user[5]/.loginshell" or warn "unlink: $!"
220 warn qq{unlink $user[5]/.loginshell
\n};
225 symlink FROZEN_SHELL
, "$user[5]/.loginshell"
226 or warn "symlink: $!";
228 warn qq{symlink FROZEN_SHELL
, "$user[5]/.loginshell"\n}
231 push @
{ $$store{$u}{modules
} }, 'login';
233 if ( -x
"/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
236 elsif ($a =~ /^u/i) {
237 if ( $$store{$u}{shell
}) {
238 if ( -l
"$user[5]/.loginshell" or -e
"$user[5]/.loginshell" ) {
240 system("rm '$user[5]/.loginshell'");
242 warn qq{system("rm '$user[5]/.loginshell'")\n};
246 symlink($$store{$u}{shell
}, "$user[5]/.loginshell")
247 or warn "symlink: $!";
249 warn qq|symlink($$store{$u}{shell
}, "$user[5]/.loginshell")\n|;
253 @
{ $$store{$u}{modules
} } = grep {!/^login$/} @
{ $$store{$u}{modules
} };
255 if ( -x
"/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
263 # XXX handle all types of domtool privs, not just domains
264 # XXX how to restart services after that?
267 my $domains = `domtool-admin perms docelic | grep '^domain: '`;
269 my @domains = split / +/, $domains;
272 push @
{ $$store{$u}{domains
} }, $_;
274 system("domtool-admin rmdom $_")
276 warn qq|system("domtool-admin rmdom $_")\n|
281 system("domtool-rmuser $_")
283 warn qq|system("domtool-rmuser $_"\n|
286 push @
{ $$store{$u}{modules
} }, 'domtool';
289 elsif ($a =~ /^u/i) {
291 system("domtool-adduser $_")
293 warn qq|system("domtool-adduser $_")\n|
296 for ( @
{ $$store{$u}{domains
} } ) {
298 system("domtool-admin grant $u domain $_")
300 warn qq|system("domtool-admin grant $u domain $_")\n|
304 @
{ $$store{$u}{modules
} } = grep {!/^domtool$/} @
{ $$store{$u}{modules
} };
314 for ( PUBLIC_ACCESS
) {
315 if ( qx{ssh
-K
$_ grep -E
'^$u\$' /etc/cron
.allow
}) {
316 push @
{ $$store{$u}{cron
} }, $_;
319 qx{ssh
-K
$_ perl
-ni
-e
'print unless /^\$/' /etc/cron
.allow
}
321 warn qq{ssh
-K
$_ perl
-ni
-e
'print unless /^\$/' /etc/cron
.allow
\n}
326 push @
{ $$store{$u}{modules
} }, 'cron';
329 elsif ($a =~ /^u/i) {
330 for ( @
{ $$store{$u}{cron
} } ) {
332 qx{ssh
-K
$_ sh
-c
'echo $u >> /etc/cron.allow'};
334 warn qq{ssh
-K
$_ sh
-c
'echo $u >> /etc/cron.allow'\n};
338 @
{ $$store{$u}{modules
} } = grep {!/^cron$/} @
{ $$store{$u}{modules
} };
347 for ( PUBLIC_ACCESS
) {
349 qx{ssh
-K
$_ slay
$u}; sleep 5; qx{ssh
-K
$_ slay
-9 $u};
351 warn qq|ssh
-K
$_ slay
$u; sleep 5; ssh
-K
$_ slay
-9 $u\n|
355 push @
{ $$store{$u}{modules
} }, 'slay';
358 elsif ($a =~ /^f/i) {
359 @
{ $$store{$u}{modules
} } = grep {!/^slay$/} @
{ $$store{$u}{modules
} };