#!/usr/bin/perl -w
# $Id$
# This script (sophomorix-add) 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 IMAP::Admin;
use DBI;
use Net::LDAP;
use Sophomorix::SophomorixPgLdap qw(show_modulename
                                    check_connections
                                    db_connect
                                    db_disconnect
                                    create_user_db_entry
                                    backup_user_database
                                    forbidden_login_hash
                                    set_sophomorix_passwd
                                    create_class_db_entry
                                    add_newuser_to_her_projects
                                   );

my @arguments = @ARGV;

my $zeit=&zeit_stempel();
my $pg_timestamp=&pg_timestamp();
my $user_nummer=0;

my $identifier;
my $nachname;
my $vorname;
my $gebdat;
my $gecos="";
my $wunsch_login;
my $wunsch_passwort;
my $wunsch_id;
my $wunsch_gid;
my $unid;
my $login_teil_1="";
my $login_teil_2="";
my $login_name_to_check="";
my $login_name_to_check_mod="";
my $login_name_ok="";
my $klartext_passwort="";
my $new_admin_group="";
my $shell="";


my @users_for_quota=();
my %forbidden_login_hash=();

my $dev_null="1>/dev/null 2>/dev/null";

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

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

# Parsen der Optionen
my $testopt=GetOptions(
           "test|t" => \$DevelConf::testen,
           "users|user|u=s" => \$loginname,
           "class|classes|c=s" => \$gruppe,
           "verbose|v+" => \$Conf::log_level,
           "system|s" => \$DevelConf::system,
           "password" => \$password,
           "info|i" => \$info,
           "lock" => \$lock,
           "unlock" => \$unlock,
           "help|h" => \$help
          );

# 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-add adds users from the file sophomorix.add to the sophomorix 
database and the authentification system.

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

Please see the sophomorix-add(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;
}


# --info
if ($info==1) {
   my $count=0;
   # Ausgabe der Anlegbaren user
   print "\nThe following users can be added:\n";
   print "(Fields with --- are automatically created by sophomorix-add):\n\n";
   printf "%-11s%-29s%-10s%-6s%-6s%-10s%-10s\n",
          "AdminClass",
          "Identifier",
          "login",
          "id",
          "gid",
          "Old-Pass",
          "unid";
   &linie;

   open(SOPHOMORIXADD,"${DevelConf::ergebnis_pfad}/sophomorix.add") || 
             die "Fehler: sophomorix.add not found!";

   my @lines=();
   while(<SOPHOMORIXADD>){
       push @lines, $_;
   }
   close(SOPHOMORIXADD);

   my @sorted_lines = sort {
       my @a_fields = split /::/, $a;
       my @aa_fields = split /;/, $a_fields[1];

       my @b_fields = split /::/, $b;
       my @bb_fields = split /;/, $b_fields[1];

       $a_fields[0] cmp $b_fields[0]  # string sort on 1st field, then
         ||
       $aa_fields[0] cmp $bb_fields[0]  # string sort on 2nd field
         ||
       $aa_fields[1] cmp $bb_fields[1]  # string sort on 3rd field
   } @lines;

   foreach my $line (@sorted_lines){
       chomp($line);
       $count++;
       ($new_admin_group,
       $identifier,
       $wunsch_login,
       $wunsch_passwort,
       $wunsch_id,
       $wunsch_gid,
       $unid)=split("::",$line);

       my $identifier_cut=substr($identifier,0,28);
       printf "%-11s%-29s%-10s%-6s%-6s%-10s%-10s\n",
              "$new_admin_group",
              "$identifier_cut",
              "$wunsch_login",
              "$wunsch_id", 
	      "$wunsch_gid",
              "$wunsch_passwort",
              "$unid";
   }
   &linie;
   print "$count users can be added\n";
   exit;
}


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

# --gruppe
if ($gruppe ne "") {
  #
  print "Gruppe/Klasse $gruppe angegeben.\n";
}

&check_connections();
&log_script_start(@arguments);


################################################################################
# Start
################################################################################
my $epoche_jetzt=time;
   print "Epochenzeit:                                 ",
         "$epoche_jetzt (right now)\n";

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

################################################################################
# User (Schüler und Lehrer) anlegen aus sophomorix.add
################################################################################

# repair.directories einlesen ???
&get_alle_verzeichnis_rechte();

# fetch permission for all homes
&fetch_repairhome();


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


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

my @passwort_zeichen=&get_passwd_charlist();


# ===========================================================================
# Alle vorhandenen Loginnamen einlesen 
# ===========================================================================
%forbidden_login_hash=&forbidden_login_hash();

# Datei mit den Schülern, die nicht angelegt wurden
open(NOCHANLEGEN,">${DevelConf::ergebnis_pfad}/sophomorix.add.new") 
    || die "Fehler: $!";
open(SOPHOMORIXADD,"${DevelConf::ergebnis_pfad}/sophomorix.add") 
    || die "Fehler: $!";
while(<SOPHOMORIXADD>){
   chomp();
   # absplitten von Wunschlogin und Wunschpasswort
   ($new_admin_group,
    $identifier,
    $wunsch_login,
    $wunsch_passwort,
    $wunsch_id,
    $wunsch_gid,
    $unid)=split("::");

   if (not defined $unid){
       $unid="";
   }

   ($nachname,$vorname,$gebdat)=split(";", $identifier);
   # GECOS-Feld:
   $gecos=$vorname." ".$nachname; 

   # Falls Wunsch-Login-Namen gegeben, diesen benutzen
   if ($wunsch_login ne "---"){
    $login_name_to_check="$wunsch_login";
   } else {
    # Sonst: Login-Name bilden
    # remove - from login
    my $nachname_login=$nachname;
    $nachname_login=~s/-//g;
    $nachname_login=&recode_to_ascii($nachname_login);

    my $vorname_login=$vorname;
    $vorname_login=~s/-//g;
    $vorname_login=&recode_to_ascii($vorname_login);

    # vorneme+nachname oder nachname+vorname
    if (defined $Conf::reverse_loginname_creation
        and $Conf::reverse_loginname_creation eq "yes"){
        $login_teil_2=substr($nachname_login,0,$Conf::schueler_login_nachname_zeichen);
        $login_teil_1=substr($vorname_login,0,$Conf::schueler_login_vorname_zeichen);
    } else {
        $login_teil_1=substr($nachname_login,0,$Conf::schueler_login_nachname_zeichen);
        $login_teil_2=substr($vorname_login,0,$Conf::schueler_login_vorname_zeichen);
    }

    # Zu prüfender Loginname
    $login_name_to_check="$login_teil_1"."$login_teil_2";
    $login_name_to_check=~tr/A-Z/a-z/; # in Kleinbuchstaben umwandeln
   }

   # Existenz prüfen ???ldap
   if (not exists($forbidden_login_hash{$login_name_to_check})){
       # Wenn der zu prüfende Login-Name nicht schon vorhanden
       # Kann er benutzt werden 
       $login_name_ok=$login_name_to_check;
   } elsif ($wunsch_login ne "---"){
       # Wunsch-Login angegeben
       # Abbrechen, Wunsch-Login soll nicht verändert werden
       print "\nERROR:\n";
       print "Cannot add user   $vorname $nachname \n";
       print "Login   $wunsch_login   exists in the system",
             "/is a forbidden name!\n\n";
       next;
   } else {
       # Wenn schon vorhanden, muss Login-Namen modifiziert werden
       $login_name_to_check_mod="$login_name_to_check";
       my $i=1; # Erster Wert für Zusatzziffer
       while (exists($forbidden_login_hash{$login_name_to_check_mod})) { 
          # An zu prüfenden Loginame eine Ziffer anhängen
          $login_name_to_check_mod="$login_name_to_check"."$i";
          $i=$i+1;
       }
       # Nun kann modifizierter Loginname benutzt werden
       $login_name_ok=$login_name_to_check_mod;
   }


   # Login-Name des anzulegenden users darf ab jetzt nicht mehr verwendet werden,
   # deshalb dem Hash mit vorhandenen Loginnamen hinzufügen
   $forbidden_login_hash{$login_name_ok}="neu";


   # Klartext-Passwort
   if ($wunsch_passwort eq "---") {
       $klartext_passwort=&get_plain_password($new_admin_group,@passwort_zeichen);
   } else {
       $klartext_passwort=$wunsch_passwort
   }

   # Shell
   $shell=&shell_ermitteln($new_admin_group);

   # Abbruch, wenn nicht der richtige loginname angelegt wird
   if ($loginname ne "") {
     if ($login_name_to_check ne $loginname) {
        print "##### $login_name_to_check wird nicht angelegt!\n";
        print NOCHANLEGEN "$_\n";
        next;
     }
   }

   # Abbruch, wenn nicht die richtige klasse angelegt wird
   if ($gruppe ne "") {
     if ($new_admin_group ne $gruppe) {
        print "$login_name_to_check ($new_admin_group) wird nicht angelegt!\n";
        print NOCHANLEGEN "$_\n";
        next;
     }
   }

   # Nun wird der User angelegt
   $user_nummer++;

   # Ermittelte Daten ausgeben
   if($Conf::log_level>=1){
      print "\n";
      &titel("Creating User $user_nummer :");
      print("Nachname:         $nachname\n");
      print("Vorname:          $vorname\n");
      print("Geburtsdatum:     $gebdat\n");
      print("Identifier:       $identifier\n");
      print("AdminClass:       $new_admin_group\n"); # lehrer oder klasse
      if ($wunsch_gid ne "---"){
         print("Unix-gid:         $wunsch_gid\n"); # lehrer oder klasse
      }
      print("GECOS:            $gecos\n");
      print("Login (prüfen):   $login_name_to_check\n");
      print("Login (OK):       $login_name_ok\n");
      print("Passwort:         $klartext_passwort\n");
      if ($wunsch_id ne "---"){
         print("Unix-id:          $wunsch_id\n");
      }
      if ($new_admin_group eq ${DevelConf::teacher}) {
         # Es ist ein Lehrer
         print("Shell (teachers):  $shell\n"); 
      } else {
         # Es ist ein Schüler
         print("Shell (students):  $shell\n"); 
      }
   }

   # 0. rember login to set quota later
   push @users_for_quota, $login_name_ok;

   # 1. Entry in sophomorix database and auth system
   &create_user_db_entry($nachname,
                         $vorname,
                         $gebdat,
                         $new_admin_group,
                         $login_name_ok,
                         $klartext_passwort,
                         $shell,
                         "",
                         $unid,
                         $epoche_jetzt,
                         $pg_timestamp,
                         "",
                         $wunsch_id,
                         undef,
                         undef,
                         undef,
                         $wunsch_gid
                        );

  # 2. Add the class (db and dirs,links, ...)
  &provide_class_files($new_admin_group);

  # 3. set a password
  &set_sophomorix_passwd($login_name_ok,$klartext_passwort);

  # 4. Creating dirs, links for the user
  &provide_user_files($login_name_ok,$new_admin_group);

  # 5. Add user to all groups she is in (files must exist to create links)
  &add_newuser_to_her_projects($login_name_ok,$new_admin_group);

  # 6. Add a mailbox for the user
  &imap_create_mailbox($imap,$login_name_ok);
}

close(SOPHOMORIXADD);
close(NOCHANLEGEN);

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


# Webmin neu starten
#if (-e "/etc/init.d/webmin"){
#   &do_falls_nicht_testen("/etc/init.d/webmin restart");
#}


if ($user_nummer==0){ 
    &titel("NOT creating userlists (0 users added)");
} else {
    &sophomorix_print();
}


# sophomorix.add moven, damit sie nicht nochmal eingelesen werden kann
&backup_amk_file($zeit,"add","after","mv");

# ===========================================================================
# add.pdf, add.csv mitloggen
# ===========================================================================
&do_falls_nicht_testen(
    "cp ${DevelConf::druck_pfad}/add.pdf ${DevelConf::log_pfad}/${zeit}.add.pdf",
    "chmod 600 ${DevelConf::log_pfad}/${zeit}.add.pdf",
    "chown root:root ${DevelConf::log_pfad}/${zeit}.add.pdf",

    "cp ${DevelConf::druck_pfad}/add.csv ${DevelConf::log_pfad}/${zeit}.add.csv",
    "chown root:root ${DevelConf::log_pfad}/${zeit}.add.pdf"
		     );



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


# Setting Quota
if ($Conf::use_quota eq "yes" 
    and $user_nummer>0
    and $user_nummer<101) {
    my $users=join(",",@users_for_quota);
    system("${DevelConf::executable_pfad}/sophomorix-quota --skiplock --users $users --noninteractive");
    &nscd_stop();
} elsif ($Conf::use_quota eq "yes" and $user_nummer>100){
    system("${DevelConf::executable_pfad}/sophomorix-quota --skiplock --students --teachers --noninteractive");
    &nscd_stop();
} else {
    if ($user_nummer==0){ 
        &titel("NOT setting quota (0 users added)");
    } else {
        &titel("NOT setting quota");
    }
}

# 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 added)");
}



&db_disconnect($dbh);
&imap_disconnect($imap);

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



################################################################################
# Subroutinen
################################################################################

# ===========================================================================
# sophomorix-print aufrufen
# ===========================================================================
sub sophomorix_print {
   if ($DevelConf::testen==0) {
      # Mit 1mal sophomorix-print aufrufen sollte es tun, tut es aber nicht
      system("$DevelConf::executable_pfad/sophomorix-print --skiplock");
      &nscd_stop();
   } else {
       &titel("NOT generating print data (Test)");
#      print "\n\nDruckdaten werden NICHT erzeugt: Testmodus\n\n"; 
   }
}


# ===========================================================================
# Shell ermitteln
# ===========================================================================
sub shell_ermitteln {
   my ($gruppe)=@_;
   my $login_shell;
   # Shell ermitteln (Lehrer <> Schüler)
      if ($gruppe eq ${DevelConf::teacher}) {
         # Es ist ein Lehrer
         if ($Conf::lehrer_per_ssh eq "yes") {
             $login_shell="/bin/bash";
           } else {
             $login_shell="/bin/false";
           }
       } else {
         # Es ist ein Schüler
         if ($Conf::schueler_per_ssh eq "yes") {
             $login_shell="/bin/bash";
           } else {
             $login_shell="/bin/false";
	   }
       }
 return $login_shell;
}





# old, not used

# ===========================================================================
# Eintrag Users in webmin machen
# ===========================================================================
sub create_webmin_account {
   my ($user)=@_;
   # files to modify
   my $wmsu="/etc/webmin/miniserv.users";
   my $webminacl="/etc/webmin/webmin.acl";

   # miniserv.users entry
   if (not -e $wmsu){
       system ("install -d /etc/webmin");
       system ("touch $wmsu");
   }
   # append entry
   open (MINISERVUSERS, ">>$wmsu") ||   die "Cannot open $wmsu \n";
   if ($DevelConf::testen==0) {
      # Ausführen: User anhängen
      print MINISERVUSERS "$user:x:0::\n";
   } else {
      # Ausgeben
      print "Appending Line to $wmsu (Test):   $user:x:0::\n";
   }
   close(MINISERVUSERS);


   # webmin.acl entry
   if (not -e $webminacl){
       system ("install -d /etc/webmin");
       system ("touch $webminacl");
   }
   # append entry
   open (WEBMINACL, ">>$webminacl") ||   die "Cannot open $webminacl \n";
   if ($DevelConf::testen==0) {
      # Ausführen: User anhängen
      print WEBMINACL "$user:\n";
   } else {
      # Ausgeben
      print "Appending Line to $webminacl (Test):   $user:\n";
   }
   close(WEBMINACL);
}


