#!/usr/bin/perl -w
# $Id$
# This script (sophomorix-kill) is maintained by Rüdiger Beck
# It is Free Software (License GPLv3)
# If you find errors, contact the author
# jeffbeck@web.de  or  jeffbeck@gmx.de


# Bibliotheken
use strict;
use Quota;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use Sophomorix::SophomorixConfig;
use Sophomorix::SophomorixBase;
use DBI;
use Net::LDAP;
use IMAP::Admin;
use Sophomorix::SophomorixPgLdap qw(show_modulename
                                    check_connections
                                    deleteuser_from_all_projects
                                    backup_user_database
                                    remove_user_db_entry
                                    pg_get_group_list
                                    db_connect
                                    db_disconnect
                                    fetchdata_from_account
                                   );

my @arguments = @ARGV;

my $zeit=&zeit_stempel;
my $user_nummer=0;

my $identifier_to_kill="";
my $login_name_to_kill="";
my $home_verzeichnis_to_kill="";
my $linux_gruppe_to_kill="";
my $public_html_to_kill="";
my $linie_to_kill="";
#my %protokoll_hash=();

my $killuser="";

my $k="";
my $v="";

# ===========================================================================
# Optionen verarbeiten
# ===========================================================================

# Variablen für Optionen
$DevelConf::testen=0;
$Conf::log_level=1;
my $help=0;
my $info=0;
my $loginname="";
my $lock=0;
my $skiplock=0;
my $unlock=0;

# Parsen der Optionen
my $testopt=GetOptions(
           "test" => \$DevelConf::testen,
           "user|u=s" => \$loginname,
           "killuser=s" => \$killuser,
           "verbose|v+" => \$Conf::log_level,
           "info|i" => \$info,
           "skiplock" => \$skiplock,
           "help|h" => \$help,
           "lock" => \$lock,
           "unlock" => \$unlock,
          );

# Prüfen, ob Optionen erkannt wurden, sonst Abbruch
&check_options($testopt);


# --help
if ($help==1) {
   # Scriptname ermitteln
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   # Befehlsbeschreibung
   print('
sophomorix-kill removes users from the sophomorix database and deletes their data for good.

Options
  -h  / --help
  -v  / --verbose
  -vv / --verbose --verbose
  -i  / --info
  -u  user / --user user
  --lock / --unlock

  --user user    (Kill only this user, from the list of killable users)
  --killuser user (Kill a user, even if she is not in sophomorix.kill)

Please see the sophomorix-kill(8) man pages for full documentation
');
   print "\n";
   exit;
}


# --unlock
if ($unlock==1) {
    &unlock_sophomorix();
    exit;
}

# --lock
if ($lock==1) {
    &lock_sophomorix("lock",0,@arguments);
    exit;
}


# --loginname
if ($loginname ne "") {
  #
  print "Loginname $loginname angegeben.\n";
}

&log_script_start(@arguments);

# ===========================================================================
# Abbruch, wenn sophomorix.kill fehlt oder leer
# ===========================================================================
if (not (-s "${DevelConf::ergebnis_pfad}/sophomorix.kill")
    and $killuser eq "") {
  &log_script_exit("No users to kill!",1,1,0,@arguments);
}


# --info
if ($info==1) {
    my $count=0;
    # Ausgabe der loeschbaren user
    print "\nThe following users can be killeden:\n\n";
    print "Loginname   AdminClass   Identifier\n";
    &linie;

    my @lines=();
    open(KILL,"${DevelConf::ergebnis_pfad}/sophomorix.kill") || die "Fehler: $!";
    while(<KILL>){      
       chomp();
       # Klasse ermitteln
       ($identifier_to_kill, 
        $login_name_to_kill,
        $linux_gruppe_to_kill)=split(/::/);
       #($linux_gruppe_to_kill)=&pg_get_group_list($login_name_to_kill);
       push @lines, "$_"."::"."$linux_gruppe_to_kill"."\n";
    }
    close(KILL);

    my @sorted_lines = sort {
        my @a_fields = split /::/, $a;
        my @b_fields = split /::/, $b;
 
        $a_fields[2] cmp $b_fields[2]  # string sort on 1st field, then
          ||
        $a_fields[1] cmp $b_fields[1]  # string sort on 2nd field
    } @lines;

    foreach my $line (@sorted_lines){
       chomp($line);
       $count++;
       ($identifier_to_kill,
        $login_name_to_kill,
        $linux_gruppe_to_kill)=split(/::/,$line);
       printf "%-11s %-12s %-40s\n",
              "$login_name_to_kill",
              "$linux_gruppe_to_kill",
              "$identifier_to_kill";
    }

    &linie;
    print "$count users can be killed\n";
    &log_script_exit("",1,1,0,@arguments);
}


&check_connections();
my $imap=&imap_connect("localhost",${DevelConf::imap_admin});

# sophomorix database sichern
&backup_user_database($zeit, "before-kill.sql");
# sophomorix.kill mitloggen
&backup_amk_file($zeit,"kill","before");

# ===========================================================================
# Protokolldatei lesen
# ===========================================================================
#%protokoll_hash=&protokoll_linien();
#%protokoll_hash=&get_protokoll_lines();


# ===========================================================================
# Datei mit den Schülern, die nicht gelöscht wurden
# ===========================================================================
if (not -e "${DevelConf::ergebnis_pfad}/sophomorix.kill.neu" ){
    system("mkdir -p ${DevelConf::ergebnis_pfad}");
    system("touch ${DevelConf::ergebnis_pfad}/sophomorix.kill.neu");
}
open(YETTOKILL,">${DevelConf::ergebnis_pfad}/sophomorix.kill.neu") 
     || die "Fehler: $!";


# ===========================================================================
# Datei mit den zu löschenden Schülern
# ===========================================================================
&titel("Beginn deletion ...");

if ($killuser ne ""){
    # killing users given by option --killuser
    print "Killing the following List of users: $killuser\n";
    my @userlist=split /,/,$killuser;
    foreach my $user_to_kill (@userlist){
        print "Killing $user_to_kill\n";
        &kill_user($user_to_kill,$identifier_to_kill);
    }
} else {
    # killing users from file
    open(KILL,"${DevelConf::ergebnis_pfad}/sophomorix.kill") || die "Fehler: $!";
    while(<KILL>){
       chomp();
       ($identifier_to_kill, $login_name_to_kill)=split(/::/);
       &kill_user($login_name_to_kill,$identifier_to_kill);
    }
    close(KILL);
    close(YETTOKILL);
}




# ===========================================================================
# Datei-Zustand (NACHHER) mitloggen 
# ===========================================================================
# sophomorix.kill mitloggen
&backup_amk_file($zeit,"kill","after");

# ===========================================================================
# Falls nur getestet wird, darf die Datei nicht ersetzt werden
# ===========================================================================
if ($DevelConf::testen==0) {
   # Kein Test
   rename("${DevelConf::ergebnis_pfad}/sophomorix.kill.neu",
          "${DevelConf::ergebnis_pfad}/sophomorix.kill" );
 } else {
   # Test
   system("rm -f ${DevelConf::ergebnis_pfad}/sophomorix.kill.neu > /dev/null");
 }


# webmin
# dont restart webmin (this is done later)
# webmin not needed anymore
#&do_falls_nicht_testen("sophomorix-webmin --modules --no-restart");


# Creating Mailing Aliases and Lists
if ($user_nummer>0) {
    system("${DevelConf::executable_pfad}/sophomorix-mail --skiplock");
    &nscd_stop();
} else {
    &titel("NOT creating mailaliases/lists (0 users removed)");
}


&imap_disconnect($imap);

&titel("$user_nummer users killed");
&log_script_end(@arguments);




############################################################
# subs
############################################################

sub kill_user {
     my ($login_name_to_kill,$identifier_to_kill) = @_;
     if (not defined $identifier_to_kill){
         $identifier_to_kill="";
     }
     # Home-Verzeichnis,klasse ermitteln
     my ($home_verzeichnis_to_kill,
         $type_to_kill,
         $gecos_to_kill,
         $linux_gruppe_to_kill,
         $uidnumber,
         $sambahomepath,
         $firstpassword,
         $sambaacctflags,
         $exitadminclass,
         $sambahomedrive,
         $sambakickofftime,
         $sambalmpassword,
         $sambalogofftime,
         $sambalogontime,
         $sambantpassword,
         $sambaprimarygroupsid,
         $sambapwdcanchange,
         $sambapwdlastset,
         $sambapwdmustchange,
         $sambasid,
         $surname,
         $firstname,
         $userpassword,
         $loginshell,
         $gidnumber) = 
         &fetchdata_from_account($login_name_to_kill);
     my $home_share_dir=$home_verzeichnis_to_kill."/".$Language::share_dir;
     my $home_task_dir=$home_verzeichnis_to_kill."/".$Language::task_dir;


     if ($home_verzeichnis_to_kill eq ""){
         print "Cannot kill user $login_name_to_kill (nonexisting?)\n";
         return 0;
     }

     if($Conf::log_level>=2){
         print "Type of user to kill is $type_to_kill\n";
     }

     my $public_html_to_kill ="";
     if ($type_to_kill eq "teacher"){
         $public_html_to_kill = ${DevelConf::www_teachers}.
                                "/".$login_name_to_kill;
     } elsif ($type_to_kill eq "student") {
         $public_html_to_kill = ${DevelConf::www_students}.
                                "/".$login_name_to_kill;
     } elsif ($type_to_kill eq "attic") {
         # find out type before attic
         if ($exitadminclass eq ${DevelConf::teacher}){
             # it was a teacher
             $public_html_to_kill = ${DevelConf::www_teachers}.
                                    "/".$login_name_to_kill;
         } else {
             # it was a student
             $public_html_to_kill = ${DevelConf::www_students}.
                                    "/".$login_name_to_kill;
         }
     } else {
         print "Not deleting public_html (Type is $type_to_kill)\n";
     }

     # Skip when login was specified by option in sophomorix-kill
     # and user to be killed is not one of these
     if ($loginname ne "") {
       if ($login_name_to_kill ne $loginname) {
          if($Conf::log_level>=2){
              print "$login_name_to_kill will  NOT ",
                    "be deleted(not given by option).\n";
          }
          # Merken, dass user noch nicht entfernt wird
          print YETTOKILL "$_\n";
          return 0;
       }
     }

     # Jetzt wird gelöscht
     # Ermittelte Daten ausgeben
     $user_nummer++;
      if($Conf::log_level>=1){
        &titel("Removing User $user_nummer:");
        print("Identifier:          $identifier_to_kill\n");
        print("Login-Name:          $login_name_to_kill\n");
        print("Home-Verzeichnis:    $home_verzeichnis_to_kill\n");
        print("Share Verzeichnis:   $home_share_dir\n");
        print("Task Verzeichnis:    $home_task_dir\n");
        print("Klasse:              $linux_gruppe_to_kill\n");
        print("public_html:         $public_html_to_kill\n");
      } 


      # delete bind mounts ???temporary
      my $unbind_command="sophomorix-bind --quick --logout ".
                         "--host unknown --user  $login_name_to_kill".
                         " --homedir $home_verzeichnis_to_kill";
      print "$unbind_command\n";
      system($unbind_command);
      # end temporary ???

      # kill mailbox
      &imap_kill_mailbox($imap,$login_name_to_kill);

      # memberships
      &deleteuser_from_all_projects($login_name_to_kill);

      # removing bind mounts if necessary
      if ($DevelConf::share_pointer_type eq "bind"){
          &delete_bind_to_all_subdirs($home_share_dir);
          &delete_bind_to_all_subdirs($home_task_dir);
      }

      
      if ($home_verzeichnis_to_kill=~/^\/home\//){
          if ($DevelConf::testen==0) {
	     print "   Removing recursively $home_verzeichnis_to_kill \n";
             &unlink_immutable_tree("$home_verzeichnis_to_kill"); 
	 } else {
	     print "Test: Removing recursively $home_verzeichnis_to_kill \n";
         }
      }

      # remove public_html in /var/www
      if ($public_html_to_kill ne ""){
          if ($DevelConf::testen==0) {
	      print "   Removing recursively $public_html_to_kill \n";
              system("rm -rf $public_html_to_kill");
   	  } else {
	      print "Test: Removing recursively $public_html_to_kill \n";
          }
      }

      # Zeile aus user_db entfernen
      if ($DevelConf::testen==0) {
         &remove_user_db_entry($login_name_to_kill);
         &archive_log_entry($login_name_to_kill,$uidnumber,$surname,$firstname);
      } else {
        print "Test: Removing $login_name_to_kill from the",
              " sophomorix database.\n"
      }
 
      # webmin
#      &remove_webmin_account($login_name_to_kill);


     if ($type_to_kill eq "teacher"){

     } elsif ($type_to_kill eq "student") {
         # Klassenverzeichnis in denen die Homes sind löschen, falls leer
         &do_falls_nicht_testen(
             "rmdir --ignore-fail-on-non-empty ${DevelConf::homedir_pupil}/$linux_gruppe_to_kill > /dev/null",
          # Klassentauschverzeichnis löschen, falls leer 
          # (Wird nicht mehr gelöscht, da wenn nichts drinsteht trotzdem nach user in der klasse sein können)
          #"rmdir --ignore-fail-on-non-empty ${DevelConf::share_classes}/$linux_gruppe_to_kill > /dev/null"
          );
     } elsif ($type_to_kill eq "examaccount") {
         &do_falls_nicht_testen(
             "rmdir --ignore-fail-on-non-empty ${DevelConf::homedir_ws}/$linux_gruppe_to_kill > /dev/null",
          );

     } else {

     }
#      if ($linux_gruppe_to_kill ne ${DevelConf::teacher}) {
#         # Es ist ein Schüler
#       }

      # Sonst noch löschen:

      # find über die Tauschverzeichnisse, löschen (evt. noch aufheben)

      # Wenn letzter Schüler einer Klasse gelöscht wird:
      # Die Gruppe Klasse löschen (kein Nachteil, wenn die Gruppe erhalten bleibt)

      # Evtl. gefährlich:
      # Klassentauschverzeichnis löschen, wenn letzter Schüler gelöscht???

      # Logonscript für Klasse entfernen (nicht so wichtig)
      # Wenns die Klasse wiedergibt, wirds überbügelt
      # Wenns die Klasse nicht mehr gibt, liegt halt a bissle Müll herum
}





# old, not used anymore
# ===========================================================================
# sub
# ===========================================================================
sub remove_webmin_account {
   my ($user)=@_;
   my $wmsu="/etc/webmin/miniserv.users";
   my $webminacl="/etc/webmin/webmin.acl";
   my @miniserv_users=();
   my @webmin_acl=();
   
   # removing miniserv.users entry
   if (-e $wmsu){
      # remove entry
      open (MINISERVUSERS, "<$wmsu") || die "Cannot open  $wmsu\n";
      open (TMP, ">$wmsu.tmp") || die "Cannot open $wmsu.tmp\n";
      while(<MINISERVUSERS>){ 
         if (/^$user:/){ # wenn die user-Zeile gefunden
             # nix tun
         } else {
	      print TMP $_;
         }
      }
      close(MINISERVUSERS);
      close(TMP);
      # really do it
      if ($DevelConf::testen==0) {
          system("mv $wmsu.tmp $wmsu");
      } else {
         print "Test: removing user from $wmsu\n";
         # remove tmp?         
      }
   }


   # removing webmin.acl entry
   if (-e $webminacl){
      # remove entry
      open (WEBMINACL, "<$webminacl") || die "Cannot open $webminacl\n";
      open (TMP, ">$webminacl.tmp") || die "Cannot open $webminacl.tmp\n";
      while(<WEBMINACL>){ 
         if (/^$user:/){ # wenn die user-Zeile gefunden
             # nix tun
         } else {
	      print TMP $_;
         }
      }
      close(WEBMINACL);
      close(TMP);
      # really do it
      if ($DevelConf::testen==0) {
          system("mv $webminacl.tmp $webminacl");
      } else {
         print "Test: removing user from $webminacl\n";
         # remove tmp?         
      }
  }


  # removing users acl, if existing 
  if (-e "/etc/webmin/custom/${user}.acl"){
        &do_falls_nicht_testen(
          "rm /etc/webmin/custom/$user.acl"
        );
  }
}
