Add freeze, frozen_shell, hcoop-kprop.
authormwolson_admin <mwolson_admin@deleuze.hcoop.net>
Thu, 17 Jul 2008 14:53:55 +0000 (10:53 -0400)
committermwolson_admin <mwolson_admin@deleuze.hcoop.net>
Thu, 17 Jul 2008 14:53:55 +0000 (10:53 -0400)
freeze [new file with mode: 0755]
frozen_shell [new file with mode: 0755]
hcoop-kprop [new file with mode: 0755]

diff --git a/freeze b/freeze
new file mode 100755 (executable)
index 0000000..5f0c282
--- /dev/null
+++ b/freeze
@@ -0,0 +1,363 @@
+#!/usr/bin/perl
+
+#
+# Purpose: freeze user (cancel user services except email), or unfreeze user.
+#
+# Usage (RUN AS _ADMIN USER ON DELEUZE WITHOUT SUDO):
+#
+# Display frozen users or details for one user (one user implies -verbose):
+#   freeze  [user], OR
+#   freeze --action list [--verbose] | freeze -a l   [-v | user]
+#
+# Freeze user:
+#   freeze -a f user
+#
+# Unfreeze:
+#   freeze -a u user
+# 
+#
+# How it works:
+#
+# Script invokes a list of modules, where each module knows how to
+# implement and unimplement a specific restriction. Implemented modules
+# should be listed in @modules array or they won't get called. Admin
+# can override list of modules with --modules=one,two,three.
+#
+# Modules execute in the order as specified for freeze, and in reverse
+# order for unfreeze.
+#
+# Module gets called as  &module($action, $user)  . The proper way to 
+# test for which action is requested is as shown below. (Note that
+# module adds or removes itself to the list of modules that ran on the user).
+#
+# if ($a =~ /^f/i) {
+#   ... freeze ...
+#   push @{ $$store{$u}{modules} }, 'MODULE';
+# } elsif ($a =~ /^u/i) {
+#   ... unfreeze ...
+#   @{ $$store{$u}{modules} } = grep {!/^MODULE$/} @{ $$store{$u}{modules} };
+# }
+#
+# Also each system-modifying action should be wrapped in if (!DRY) as shown:
+#
+#    if (!DRY) {
+#      system(qq{SOME COMMAND})
+#    } else {
+#      warn qq|SOME COMMAND|
+#    }
+#
+# User is valid system username, and user's getent entry is prepared and
+# retrievable through @user array, should you need some of its info.
+#
+# Module can save all persistent data to $$store{$user}{$modulename}. For 
+# example, after cron module removes user from all cron.allows, it
+# registers the machines where user was removed to 
+# @{ $$store{$u}{cron} }, so that it can revert it back if user is
+# unfreezed.
+#
+# Module 'record' creates or deletes initial user entry in $$store.
+# If you create a new module that will use the store (announce it's
+# hash key by creating it empty in record().
+#
+# For additional help, here's how the stored hash might look like:
+#
+#
+# $store = {
+#    user1 => {
+#      date => 'Sun Jun 29 18:45:43 CEST 2008',
+#      getent => [qw/docelic 1000 1000 DavorOcelic /home/docelic /bin/bash]
+#
+#      modules => [qw/login domtool cron slay/],         # (modules that ran)
+#      domains => [qw/spinlock.hr test.hr/],    # (domains that were removed)
+#      cron => [qw/mire/],       # (hosts where cron.allow entry was removed)
+#    },
+#    user2 => {
+#           ...
+#    },
+#   ...
+#   ...
+#   ...
+# }
+#
+#
+# Wiki page relating to this script is http://wiki.hcoop.net/MemberFreezing
+#
+# Davor Ocelic, docelic@hcoop.net, Sun Jun 29 18:41:02 CEST 2008
+#
+#
+
+use warnings;
+use strict;
+
+use Storable qw/lock_nstore lock_retrieve/;
+use Getopt::Long qw/GetOptions/;
+
+use constant DEBUG => 1;
+use constant DRY => 1;
+use constant STORE => "/tmp/store";
+use constant DEFAULT_SHELL => '/bin/bash';
+use constant FROZEN_SHELL => '/afs/hcoop.net/common/etc/scripts/frozen_shell';
+use constant PUBLIC_ACCESS => [qw/mire/];
+use constant RUN_SERVER => 'handgun';
+
+my $store = {}; # cached info
+my $action = 'list'; # list, freeze, unfreeze
+my $verbose = 0; # 0/1 (print which modules were applied to user in freezing)
+my $force = 0; # 0/1 (force action even if user already freezed/unfreezed)
+my $user; # who to freeze/unfreeze
+my @user; # getent passwd entry for user
+my $modules; # list of freeze/unfreeze actions to take
+my @modules; # modules, possibly overriden by split /,\s+/, $modules
+
+# Keep modules listed in order of application, honoring possible dependencies.
+@modules = (qw/record login domtool slay/);
+
+unless ( GetOptions (
+       'do|d|a=s'     => \$action,
+       'verbose|v!'   => \$verbose,
+       'force!'       => \$force,
+       'modules|m=s'  => \$modules,
+)) { die "Error parsing options: $!\n" }
+
+$user = shift ;
+
+if ( -e STORE ) {
+       $store = lock_retrieve(STORE);
+} else {
+       warn "No '" . STORE . "', skipping load.\n";
+}
+
+if ( `hostname` ne RUN_SERVER . "\n" ) {
+       die "Please run script on " . RUN_SERVER . "\n";
+}
+
+if ( $action =~ /^l/i ) {
+       while (my ($k,$v) = each %$store ) {
+               if (! $user or $user eq $k ) {
+                       print "$k          $$v{date}\n";
+                       print "  @{$$v{modules}}\n" if $verbose or $user;
+               }
+       }
+       exit 0;
+}
+
+$user or die "Must specify user to freeze/unfreeze. Exiting.\n";
+$user =~ /^[a-z0-9]+$/ or die "Invalid username (not [a-z0-9]+).\n";
+@user = split(/:/, `getent passwd $user`);
+@user or die "No such user? (getent passwd USER empty.)\n";
+chomp $user[$#user];
+
+if ( $action !~ /^[fu]/i ) {
+       warn "Unknown action: use -a [list (l)|freeze (f)|unfreeze (u)]\n";
+}
+
+if ( $action =~ /^f/i ) {
+       if ( exists $$store{$user} ) {
+               warn "User already frozen since $$store{$user}{date}.\n";
+               if (! $force) {
+                       die "Exiting.\n";
+               }
+       }
+}
+
+elsif ( $action =~ /^u/i ) {
+       if (! exists $$store{$user} ) {
+               warn "User not frozen in the first place.\n";
+               if (! $force) {
+                       die "Exiting.\n";
+               }
+       }
+}
+
+else {
+       warn "How did you get through?\n";
+       die;
+}
+
+
+if ($modules) { @modules = split /[,\s+]/, $modules; }
+for ( $action =~ /^u/i ? reverse @modules : @modules ) {
+       no strict 'refs';
+       print "Module: $_\n";
+       &{ $_ }($action, $user);
+}
+
+lock_nstore $store, STORE;
+
+
+###########################################################################
+# Helpers below
+
+# GETENT:
+#    0    1   2    3      4                    5                     6
+# docelic:x:10235:65534:docelic:/afs/hcoop.net/user/d/do/docelic:/bin/bash
+
+sub record {
+       my ($a, $u) = @_;
+       $a =~ /^f/i and $$store{$u} = {
+               date => scalar localtime,
+               getent => [ @user ],
+               modules => [],
+               domains => [],
+               cron => [],
+       };
+       $a =~ /^u/i and delete $$store{$u};
+}
+
+sub login {
+       my ($a, $u) = @_;
+
+       if ($a =~ /^f/i) {
+               if ( $user[6] ne DEFAULT_SHELL ) {
+                       $$store{$u}{shell} = $user[6] unless $user[6] eq FROZEN_SHELL;
+               }
+
+
+               if ( -e "$user[5]/.loginshell" ) {
+                       if (!DRY) {
+                               unlink "$user[5]/.loginshell" or warn "unlink: $!"
+                       } else {
+                               warn qq{unlink $user[5]/.loginshell\n};
+                       }
+               }
+                               
+               if (!DRY) {
+                       symlink FROZEN_SHELL, "$user[5]/.loginshell"
+                               or warn "symlink: $!";
+               } else {
+                       warn qq{symlink FROZEN_SHELL, "$user[5]/.loginshell"\n}
+               }
+
+               push @{ $$store{$u}{modules} }, 'login';
+
+               if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
+       }
+
+       elsif ($a =~ /^u/i) {
+               if ( $$store{$u}{shell}) {
+                       if ( -l "$user[5]/.loginshell" or -e "$user[5]/.loginshell" ) {
+                               if (!DRY) {
+                                       system("rm '$user[5]/.loginshell'");
+                               } else {
+                                       warn qq{system("rm '$user[5]/.loginshell'")\n};
+                               }
+                       }
+                       if (!DRY) {
+                               symlink($$store{$u}{shell}, "$user[5]/.loginshell")
+                                       or warn "symlink: $!";
+                       } else {
+                               warn qq|symlink($$store{$u}{shell}, "$user[5]/.loginshell")\n|;
+                       }
+               }
+
+               @{ $$store{$u}{modules} } = grep {!/^login$/} @{ $$store{$u}{modules} };
+
+               if ( -x "/usr/sbin/nscd" ) { system("sudo /usr/sbin/nscd -i passwd") };
+       }
+}
+
+
+sub domtool {
+       my ($a, $u) = @_;
+
+       # XXX handle all types of domtool privs, not just domains
+       # XXX how to restart services after that?
+
+       if ($a =~ /^f/i) {
+               my $domains = `domtool-admin perms docelic  | grep '^domain: '`;
+               chomp $domains;
+               my @domains = split / +/, $domains;
+
+               for (@domains) {
+                       push @{ $$store{$u}{domains} }, $_;
+                       if (!DRY) {
+                               system("domtool-admin rmdom $_")
+                       } else {
+                               warn qq|system("domtool-admin rmdom $_")\n|
+                       }
+               }
+
+                       if (!DRY) {
+                               system("domtool-rmuser $_")
+                       } else {
+                               warn qq|system("domtool-rmuser $_"\n|
+                       }
+
+               push @{ $$store{$u}{modules} }, 'domtool';
+       }
+
+       elsif ($a =~ /^u/i) {
+                       if (!DRY) {
+                               system("domtool-adduser $_")
+                       } else {
+                               warn qq|system("domtool-adduser $_")\n|
+                       }
+
+               for ( @{ $$store{$u}{domains} } ) {
+                       if (!DRY) {
+                               system("domtool-admin grant $u domain $_")
+                       } else {
+                               warn qq|system("domtool-admin grant $u domain $_")\n|
+                       }
+               }
+
+               @{ $$store{$u}{modules} } = grep {!/^domtool$/} @{ $$store{$u}{modules} };
+       }
+}
+
+
+
+sub cron {
+       my ($a, $u) = @_;
+
+       if ($a =~ /^f/i) {
+               for ( PUBLIC_ACCESS ) {
+                       if ( qx{ssh -K $_ grep -E '^$u\$' /etc/cron.allow }) {
+                               push @{ $$store{$u}{cron} }, $_;
+
+                               if (!DRY) {
+                                       qx{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow }
+                               } else {
+                                       warn qq{ssh -K $_ perl -ni -e 'print unless /^\$/' /etc/cron.allow\n}
+                               }
+                       }
+               }
+
+               push @{ $$store{$u}{modules} }, 'cron';
+       }
+
+       elsif ($a =~ /^u/i) {
+               for ( @{ $$store{$u}{cron} } ) {
+                       if (!DRY) {
+                               qx{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'};
+                       } else {
+                               warn qq{ssh -K $_ sh -c 'echo $u >> /etc/cron.allow'\n};
+                       }
+               }
+
+               @{ $$store{$u}{modules} } = grep {!/^cron$/} @{ $$store{$u}{modules} };
+       }
+}
+
+
+sub slay {
+       my ($a, $u) = @_;
+
+       if ($a =~ /^f/i) {
+               for ( PUBLIC_ACCESS ) {
+                       if (!DRY) {
+                               qx{ssh -K $_ slay $u}; sleep 5; qx{ssh -K $_ slay -9 $u};
+                       } else {
+                               warn qq|ssh -K $_ slay $u; sleep 5; ssh -K $_ slay -9 $u\n|
+                       }
+               }
+
+               push @{ $$store{$u}{modules} }, 'slay';
+       }
+
+       elsif ($a =~ /^f/i) {
+               @{ $$store{$u}{modules} } = grep {!/^slay$/} @{ $$store{$u}{modules} };
+       }
+}
+
+
diff --git a/frozen_shell b/frozen_shell
new file mode 100755 (executable)
index 0000000..01c4e7a
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+echo "
+
+Please pay your HCoop dues to have your access restored.
+
+All information on making a payment is available on our Members Portal,
+
+  https://members.hcoop.net/
+
+Contact HCoop system administrators at admins@hcoop.net if you
+cannot access the Portal.
+
+Thank you,
+HCoop
+
+";
+
+exit 0;
+
diff --git a/hcoop-kprop b/hcoop-kprop
new file mode 100755 (executable)
index 0000000..71d7850
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash -e
+
+#
+# It is important that admins receieve notification of any errors that
+# occur when running this; please don't indiscriminately send logs to
+# /dev/null.
+#
+
+export PATH=$PATH:/usr/sbin:/sbin
+
+kdb5_util dump /var/lib/krb5kdc/slave_datatrans
+kprop krunk.hcoop.net \
+   2>&1 | grep -v 'Database propagation to krunk.hcoop.net: SUCCEEDED'
+
+