#!/usr/bin/perl
# Skript zum Anlegen/Aktualisieren/Löschen von SchülerAccounts
#
# Dazu wird die Datei schuelerdaten eingelesen. 
#

##################################################################################
#
#          V A R I A B L E N D E F I N I I O N E N 
#
##################################################################################

#
# GLOBALE VARIABLEN
#
# Parse-Option
$PARSE=true;

# Nummer der gelesenen Zeile aus Schuelerdaten.tab
$ZEILE = 0;
# Nummer des erfolgreich gelesenen Datensatz aus Schuelerdaten.tab
$DATENSATZ = 0;
# Zahl der Fehler beim Lesen der Zeilen aus Schuelerdaten.tab
$FEHLER = 0;
$DATENSATZNEU = 0;
$DATENSATZNEUEGRUPPE = 0;

#
# DateiHandles
#
# Schuelerdaten
open( DATEN, "Schuelerdaten.tab" );
# Datei zur Aufnahme der Kommentare
open( LOGDATEI, ">..\log\ModifySchuelerAccounts.log" );
# Datei zur Aufnahme der Statistik
open( STATISTIC, ">..\log\ModifySchuelerAccounts.stat" );
# FehlerDatei
open( FEHLER, ">..\log\ModifySchuelerAccounts.err" );

#
# LOKALE VARIABLEN
#
my ($Password);
my ($Salt);
my ($CryptPassword);

##################################################################################
#
#          M A I N 
#
##################################################################################

# Initialisierung des Zufallszahlengenerators für die Konstruktion von Passwörtern
srand( time() ^ ( $$ + ($$ << 15)) );

ReadGROUPS();
ReadPASSWD();

while( <DATEN> ){

    ( $NewLogin, $NewIdString, $NewGroup, $NewSecondaryGroups ) = ParseDatensatz( $_ );
    if ( $NewLogin eq "false" ) {next;}

    # Für spätere Löschaktion werden die IdStrings der hier angelegten/veränderten/wiedergefundenen Schueler protokoliert
    $NewIdStringExists{$NewIdString} = $NewIdString;
        

    # Neuer Schueler ? 
    if( !$GCOS_EXISTS{ $NewIdString }  ){
	$DATENSATZNEU++;
	( $Password, $CryptPassword ) = RandomPasswordCryptPassword();
        print LOGDATEI "Lege Account für $NewIdString an:\nKlasse bzw. Stufe: $NewGroup\nBenutzername: $NewLogin\nKennwort: $Password.\n";
        system "useradd -c $NewIdString -d /home/schueler/$NewLogin -g $NewGroup -p $CryptPassword -m $NewLogin";
        system "smbpasswd -a $NewLogin $Password";
    	$LOGIN_BY_GCOS{ $NewIdString } = $NewLogin;
    	$GCOS_BY_LOGIN{ $NewLogin } = $NewIdString;
    	$GCOS_EXISTS{ $NewIdString } = $NewIdString;
    	$LOGIN_EXISTS{ $NewLogin } = $NewLogin;
    } else {
        if( $GROUPNAME_BY_GCOS{ $IdString } eq $NewGroup ){
            print LOGDATEI "Account $NewIdString bereits vorhanden.\nKlasse bzw. Stufe $NewGroup hat sich nicht geändert\n";
        } else {
            print LOGDATEI "Account $NewIdString bereits vorhanden.\n";
            print LOGDATEI "Klasse bzw. Stufe hat sich von $GROUPNAME_BY_GCOS{ $IdString } auf $NewGroup geändert.\n";            
	    $DATENSATZNEUEGRUPPE++;
            system "usermod -g $NewGroup $NewLogin";
        }
    }

    # Schueler den Sekundaeren Gruppen zuordnen
    if ( $NewSecondaryGroups ) {
        system "usermod -G $NewSecondaryGroups $NewLogin";
        print LOGDATEI "Ordne $Nachname folgenden Kursen zu: $NewSecondaryGroups\n";
    }

    print LOGDATEI "Datensatz $DATENSATZ bearbeitet\n\n"; 
}

# Alte Schueler löschen, wenn sie nicht mehr unter den neuen sind


print LOGDATEI "Lösche SchülerAccounts aus dem System die in /etc/passwd aber nicht in Schuelerdaten.tab stehen.\n";
foreach ( keys(%GCOS_EXISTS) ){
   if( !$NewIdStringExists{$_} && substr($_,0,9) eq "Schueler/" ) {
       print LOGDATEI "Lösche SchülerAccount: $_\n";
       system "userdel -r $LOGIN_BY_GCOS{$_}";
       system "smbpasswd -x $LOGIN_BY_GCOS{$_}";
   }
}

close DATEN, LOGDATEI, FEHLER, STATISTIC;

print "Verarbeitete Datensätze: $DATENSATZ\n";
print "Neue Datensätze: $DATENSATZNEU\n";
print "Primäre Gruppe geändert: $DATENSATZNEUEGRUPPE\n\n";
print "Fehlerhafte Datensätze: $FEHLER\n";

##################################################################################
#
#          S U B R O U T I N E N 
#
##################################################################################

sub ReadGROUPS {
    #
    #   Einlesen der Gruppen aus /etc/group
    #
    #   Initialisierung/Indizierung der globalen Hashes
    #
    setgrent();
    while ( @list = getgrent() ){
        ( $name, $gid ) = @list[0,2];
        $GROUPNAME_BY_GID{ $gid } = $name;
        $GID_BY_GROUPNAME{ $name } = $gid;
    }
    endgrent();
}

sub ReadPASSWD {
    #
    #   Einlesen der Accounts aus /etc/passwd
    #
    #   Initialisierung/Indizierung der globalen Hashes
    #
    setpwent();
    while ( @list = getpwent() ){
    	( $login, $uid, $gid, $gcos ) = @list[0,2,3,6];
    	$GROUPNAME_BY_GCOS{ $gcos } = $GROUPNAME_BY_GID{ $gid };
    	$UID_BY_GCOS{ $gcos } = $uid;
    	$LOGIN_BY_GCOS{ $gcos } = $login;
    	$GCOS_BY_LOGIN{ $login } = $gcos;
    	$GCOS_EXISTS{ $gcos } = $gcos;
    	$LOGIN_EXISTS{ $login } = $login;
    }
    endpwent();
}

sub ParseDatensatz  {
    #
    #   Überprüfung ob die übergebene Textzeile einen gültigen Datensatz darstellt
    #
    my ( $SecondaryGroups );

    $ZEILE++;

    # Kommentarzeilen finden und ueberspringen
    $Zeile=$_[0];
    chomp $Zeile;
    if( substr( $Zeile, 0, 1 ) eq "#") { return( false ); }
   
    #Zeile aufsplitten und die Teile dem Array @felder uebergeben
    @felder = split( /;/, $Zeile );

    # Auf mindestens 4 Felder im Datensatz prüfen
    if( $#felder<3 ){ 
	$FEHLER++;
	print FEHLER "Datensatz in Zeile $ZEILE hat zuwenig Felder\n";
        return( false );
    }

    ($Group,$Nachname,$Vorname,$Geburtsdatum) = (@felder[0..3]);   

    # Primäre Gruppe vorhanden?
    if( !$GID_BY_GROUPNAME{$Group} ){
	$FEHLER++;
	print FEHLER "Primäre Gruppe des Datensatzes in Zeile $ZEILE existiert nicht\n";
        return( false );
    }

    # Einlesen der in /etc/groups vorhandenen Gruppen
    if($#felder>=4){
	foreach $g ( @felder[4..$#felder] ) {
            if ( $GID_BY_GROUPNAME{ $g } ) {
                if ( $SecondaryGroups eq "" ){ $SecondaryGroups = $g; }
                else { $SecondaryGroups = "$SecondaryGroups,$g"; }
            } else {
                print FEHLER "Sekundäre Gruppe $g in Zeile $ZEILE nicht vorhanden\n";
	    }
        } 
    }

    # Ermitteln von Login und IdString
    $Login = lc substr($Nachname, 0, 6).substr($Vorname, 0, 2);
    $IdString = "Schueler/$Nachname,$Vorname/$Geburtsdatum";
    
    $i = 0;
    while( $LOGIN_EXISTS{$Login} && ($GCOS_BY_LOGIN{$Login} ne $IdString) ){
	print LOGDATEI "Login $Login bereits vorhanden. Verändere Login\n";
        $i++;
	$Login = substr( $Login,0,8).$i;
    }

    $DATENSATZ++;
    return( $Login, $IdString, $Group, $SecondaryGroups );
}

sub RandomPasswordCryptPassword {
    my ($Password);
    my ($Salt);
    my ($CryptPassword);
    # Zeichen aus den das Passwort bestehen kann
    my (@Zeichen);
    @Zeichen = ( "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" );

    for ( $i=0; $i<6; $i++ )
    {
        $Password = $Password.$Zeichen[ int(rand($#Zeichen-1)) ];
    }

    for ( $i=0; $i<2; $i++ )
    {
        $Salt = $Salt.$Zeichen[ int(rand($#Zeichen-1)) ];
    }

    $CryptPassword = crypt( $Password, $Salt );

    return ( $Password, $CryptPassword );
}
