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

# modules
use strict;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use Sophomorix::SophomorixConfig;
use Sophomorix::SophomorixBase;
use Net::LDAP;
use JSON;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Useqq = 1;
$Data::Dumper::Terse = 1; 
use Sophomorix::SophomorixBase qw(
                                 print_line
                                 print_title
                                 log_script_start
                                 log_script_end
                                 log_script_exit
                                 lock_sophomorix
                                 unlock_sophomorix
                                 backup_auk_file
                                 check_options
                                 config_sophomorix_read
                                 result_sophomorix_init
                                 result_sophomorix_add
                                 result_sophomorix_add_summary
                                 result_sophomorix_check_exit
                                 result_sophomorix_print
                                 run_hook_scripts
                                 );

use Sophomorix::SophomorixSambaAD qw(
                                 AD_user_move
                                 AD_bind_admin
                                 AD_dns_get
                                 AD_unbind_admin
                                 AD_user_update
                                 AD_get_groups_v
                                 AD_get_AD_for_check
                                 AD_get_passwd
                                 AD_object_search
                                 AD_get_name_tokened
                                 AD_group_addmember
                                 AD_group_removemember
                                 AD_group_update
                                    );
my @arguments = @ARGV;

my $user_count=0;


############################################################
# options
############################################################
$Conf::log_level=1;
my $help=0;
my $info=0;
my $json=0;
my $lock=0;
my $unlock=0;

# selection lookup hash
my $school_opt="";
my $class_old_opt="";
my $class_new_opt="";
my $user_opt="";
my %selection=();

my $testopt=GetOptions(
           "help|h" => \$help,
           "info|i" => \$info,           
           "json|j+" => \$json,
           "verbose|v+" => \$Conf::log_level,
           "lock" => \$lock,
           "unlock" => \$unlock,
           "user|u=s" => \$user_opt,
           "class-old=s" => \$class_old_opt,
           "class-new=s" => \$class_new_opt,
           "school=s" => \$school_opt,
          );

my %sophomorix_result=&result_sophomorix_init("sophomorix-update");
&check_options($testopt,\%sophomorix_result,$json);

# Reading Configuration
my ($ldap,$root_dse) = &AD_bind_admin(\@arguments,\%sophomorix_result,$json);
my $root_dns=&AD_dns_get($root_dse);
my %sophomorix_config=&config_sophomorix_read($ldap,$root_dse,\%sophomorix_result);
my ($smb_admin_pass)=&AD_get_passwd($DevelConf::sophomorix_file_admin,
                                     $DevelConf::secret_file_sophomorix_file_admin);
my $update_file=$sophomorix_config{'INI'}{'PATHS'}{'CHECK_RESULT'}."/sophomorix.update";
my $update_file_new=$sophomorix_config{'INI'}{'PATHS'}{'CHECK_RESULT'}."/sophomorix.update.new";



# --help
if ($help==1) {
   # get scriptname
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   print('
sophomorix-update updates the ldap entry of a user

Options
  -h  / --help
  -v  / --verbose
  -vv / --verbose --verbose
  -i  / --info

Update all users:
  sophomorix-update

Update users WITHIN their schools only:
  sophomorix-update --school <schoolname1>,<schoolname2>, ...

Update users that change schools only:
  sophomorix-update --school global


Update some users only:
  sophomorix-update --user <user1>,<user2>, ...

Update users that ARE in some classes (old class) only:
  sophomorix-update --class-old <class1>,<class2>, ...

Update users that WILL BE in some classes (new class) only:
  sophomorix-update --class-new <class1>,<class2>, ...

Please see the sophomorix-update(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 $ref_update=&read_sophomorix_update({sophomorix_config=>\%sophomorix_config});
    #print Dumper($ref_update);
    &json_dump({json => $json,
                jsoninfo => "UPDATEFILE",
                jsoncomment => "sophomorix.update",
                log_level => $Conf::log_level,
                object_name => $school_opt,
                hash_ref=>$ref_update,
                sophomorix_config=>\%sophomorix_config,
              });
    exit;
}



# --user
if ($user_opt ne ""){
    my @users=split(/,/,$user_opt);
    foreach my $us (@users){
        $selection{'SELECTION'}{'USER'}{$us}="updateuser";
    }
}



# --school
if ($school_opt ne ""){
    my @schools=split(/,/,$school_opt);
    foreach my $sch (@schools){
        $selection{'SELECTION'}{'SCHOOL'}{$sch}="updateschool";
    }
}



# --class-old
if ($class_old_opt ne "") {
    my @classes=split(/,/,$class_old_opt);
    foreach my $cl (@classes){
        $selection{'SELECTION'}{'CLASS_OLD'}{$cl}="updateclass";
    }
}



# --class-new
if ($class_new_opt ne "") {
    my @classes=split(/,/,$class_new_opt);
    foreach my $cl (@classes){
        $selection{'SELECTION'}{'CLASS_NEW'}{$cl}="updateclass";
    }
}





&result_sophomorix_check_exit(\%sophomorix_result,\%sophomorix_config,$json);

################################################################################
# Start
################################################################################
&log_script_start(\@arguments,\%sophomorix_result,\%sophomorix_config);


# logging sophomorix.update
&backup_auk_file("update","before",\%sophomorix_config);

# file for users that are NOT updated
open(STILLTOUPDATE,">$update_file_new") 
     || die "ERROR: $!";

# the update file
if (not -f $update_file){
    &result_sophomorix_add(\%sophomorix_result,"ERROR",-1,"",$update_file." not found!");
    &result_sophomorix_add_summary({
        NAME=>"ADDED", 
        RESULT=>0, 
        RESULT_TYPE => "integer",
        DESCRIPTION_POST => "users added", 
        DESCRIPTION_PRE => "added users", 
        FORMAT_TYPE => 1,
        sophomorix_result=>\%sophomorix_result,
	});
    &log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
}



# calulate users to update
my $max_user_count=`cat $sophomorix_config{'INI'}{'PATHS'}{'CHECK_RESULT'}/sophomorix.update | wc -l`;
chomp($max_user_count);

# open update file
open(USERUPDATE,"$update_file") 
     || die "ERROR: $!";

# loop through all users in sophomorix.update
while(<USERUPDATE>){
    if(/^\#/){ # skip commented lines
        next;
    }
    chomp();
    my ($user,
        $unid_old,
        $unid_new,
        $surname_ascii_old,
        $surname_ascii_new,
        $firstname_ascii_old,
        $firstname_ascii_new,
        $birthdate_old,
        $birthdate_new,
        $surname_utf8_old,
        $surname_utf8_new,
        $firstname_utf8_old,
        $firstname_utf8_new,
        $filename_old,
        $filename_new,
        $status_old,
        $status_new,
        $role_old,
        $role_new,
        $class_old,
        $class_new,
        $school_old,
        $school_new,
        $surname_initial_utf8_old,
        $surname_initial_utf8_new,
        $firstname_initial_utf8_old,
        $firstname_initial_utf8_new,
        $mail_old,
        $mail_new,
        $webui_string_old,
        $webui_string_new,
        $homedirectory_old,
        $homedirectory_new,
        $homedirectory_old_rel,
        $homedirectory_new_rel,
       )=split(/::/);


    # deselecting users
    if ($user_opt ne ""){
        if (not exists $selection{'SELECTION'}{'USER'}{$user}){
            print "   * Skipping $user in $class_old/$class_new in $school_old/$school_new (Reason: $user)\n";
            print STILLTOUPDATE "$_\n";
            next;
        }
    }

    if ($class_old_opt ne ""){
        if (not exists $selection{'SELECTION'}{'CLASS_OLD'}{$class_old}){
            print "   * Skipping $user in $class_old/$class_new in $school_old/$school_new (Reason: $class_old)\n";
            print STILLTOUPDATE "$_\n";
            next;
        }
    }

    if ($class_new_opt ne ""){
        if (not exists $selection{'SELECTION'}{'CLASS_NEW'}{$class_new}){
            print "   * Skipping $user in $class_old/$class_new in $school_old/$school_new (Reason: $class_new)\n";
            print STILLTOUPDATE "$_\n";
            next;
        }
    }

    if ($school_opt ne "" and $school_opt ne "global"){
        # option is a school
        # update as long as school does not change
        #   skip if oldschool is not in option
        #   OR
        #   skip if school is not changed (new school = "---")
        if (not exists $selection{'SELECTION'}{'SCHOOL'}{$school_old} or 
            $school_new ne "---"
           ){
            print "   * Skipping $user in $class_old/$class_new in $school_old (Reason: $school_old/$school_new)\n";
            print STILLTOUPDATE "$_\n";
            next;
        }
    } elsif ($school_opt ne "" and $school_opt eq "global"){
        # option is global
        # skip when oldschool=newschool
        if ($school_new eq "---"){
            print "   * Skipping $user in $class_old/$class_new in $school_old/$school_new (Reason: within a school)\n";
            print STILLTOUPDATE "$_\n";
            next;
        }
    }


    $user_count++;
    my ($count,$dn,$cn)=&AD_object_search($ldap,$root_dse,"user",$user);

    if ($unid_new ne "---" or 
        $surname_ascii_new ne "---" or 
        $firstname_ascii_new ne "---" or 
        $birthdate_new ne "---" or 
        $surname_utf8_new ne "---" or
        $firstname_utf8_new ne "---"  or
        $surname_initial_utf8_new ne "---" or
        $firstname_initial_utf8_new ne "---"  or
        $filename_new ne "---" or
        $status_new ne "---" or
        $mail_new ne "---" or
        $webui_string_new ne "---" or 
        $homedirectory_new ne "---" or
        $homedirectory_new_rel ne "---" 
       ){
        # do NOT update move stuff: class,role,school 
        # leave it to AD_user_move
        my @webui_calclist=("---");
        if ($webui_string_new eq "---"){
            # leave @webui_calclist 
        } else {
            @webui_calclist=split(",",$webui_string_new);
        }
        &AD_user_update({ldap=>$ldap,
                         root_dse=>$root_dse,
                         dn=>$dn,
                         user=>$user,
                         birthdate=>$birthdate_new,
                         surname_ascii=>$surname_ascii_new,
                         firstname_ascii=>$firstname_ascii_new,
                         surname_utf8=>$surname_utf8_new,
                         firstname_utf8=>$firstname_utf8_new,
                         surname_initial_utf8=>$surname_initial_utf8_new,
                         firstname_initial_utf8=>$firstname_initial_utf8_new,
                         filename=>$filename_new,
                         status=>$status_new,
                         mail=>$mail_new,
                         webui_permissions_calculated=>\@webui_calclist,
                         unid=>$unid_new,
                         homedirectory=>$homedirectory_new,
                         homedirectory_rel=>$homedirectory_new_rel,
                         user_count=>$user_count,
                         max_user_count=>$max_user_count,
                         json=>$json,
                         sophomorix_config=>\%sophomorix_config,
                         sophomorix_result=>\%sophomorix_result,
                       });
    }

    # test if user must be moved
    if ($school_new ne "---" or $class_new ne "---" or $role_new ne "---"){
        if ($school_new eq "---"){
            $school_new=$school_old;
        }
        if ($class_new eq "---"){
            $class_new=$class_old;
        }
        if ($role_new eq "---"){
            $role_new=$role_old;
        }

        my $group_token_old=&AD_get_name_tokened($class_old,$school_old,"adminclass");
        my $group_token_new=&AD_get_name_tokened($class_new,$school_new,"adminclass");
        # move the user
        &AD_user_move({ldap=>$ldap,
                       root_dse=>$root_dse,
                       root_dns=>$root_dns,
                       user=>$user,
                       unid=>$unid_new,
                       user_count=>$user_count,
                       group_old_basename=>$class_old,
                       group_new_basename=>$class_new,
                       group_old=>$group_token_old,
                       group_new=>$group_token_new,
                       school_old=>$school_old,
                       school_new=>$school_new,
                       role_old=>$role_old,
                       role_new=>$role_new,
                       filename_old=>$filename_old,
                       filename_new=>$filename_new,
                       smb_admin_pass=>$smb_admin_pass,
                       sophomorix_config=>\%sophomorix_config,
                       sophomorix_result=>\%sophomorix_result,
                      });
        if ($role_new ne $role_old){
            # add new role membership
            my $rolegroup_new="role-".$role_new;
            &AD_group_addmember({ldap => $ldap,
                                 root_dse => $root_dse,
                                 group => $rolegroup_new,
                                 addmember => $user,
                               });
            # remove old role membership
            my $rolegroup_old="role-".$role_old;
            &AD_group_removemember({ldap => $ldap,
                                    root_dse => $root_dse,
                                    group => $rolegroup_old,
                                    removemember => $user,
                                  });
        }
    }
}



close(USERUPDATE);
close(STILLTOUPDATE);

# logging sophomorix.update
&backup_auk_file("update","after",\%sophomorix_config);
rename("$update_file_new","$update_file");

&result_sophomorix_add_summary({
                     NAME=>"UPDATED", 
                     RESULT=>$user_count, 
                     RESULT_TYPE => "integer",
                     DESCRIPTION_POST => "users updated", 
                     DESCRIPTION_PRE => "updated users", 
                     FORMAT_TYPE => 1,
                     sophomorix_result=>\%sophomorix_result,
			       });



&remove_former_teachers_from_classes();


&AD_unbind_admin($ldap);



if ($user_count>0){
    &run_hook_scripts("UPDATE_HOOK_DIR",\%sophomorix_result,\%sophomorix_config,"TRUE");
} else {
    print "No users updated -> No hook scripts run.\n";
}

&log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);


############################################################
# subs
############################################################
sub remove_former_teachers_from_classes {
    &print_title("START: Removing former teachers from classes");
    my $school;
    if ($school_opt eq ""){
        $school=$DevelConf::name_default_school;
    } else {
        $school=$school_opt;
    }

    # get all classes
    my $ref_groups_v=&AD_get_groups_v({ldap=>$ldap,
                                       root_dse=>$root_dse,
                                       root_dns=>$root_dns,
                                       school=>$school_opt,
                                       sophomorix_config=>\%sophomorix_config,
                                     });


    my ($ref_AD_check) = &AD_get_AD_for_check({ldap=>$ldap,
                                           root_dse=>$root_dse,
                                           root_dns=>$root_dns,
                                           admins=>"FALSE",
                                           sophomorix_config=>\%sophomorix_config,
                                         });
    foreach my $group ( @{ $ref_groups_v->{'LISTS'}{'GROUP_by_sophomorixSchoolname'}{$school}{'class'} }){
        print "Testing $group for former teachers\n";
	@{ $ref_groups_v->{'GROUPS'}{$group}{'sophomorixAdmins'} } = sort @{ $ref_groups_v->{'GROUPS'}{$group}{'sophomorixAdmins'} };
	my @admins = @{ $ref_groups_v->{'GROUPS'}{$group}{'sophomorixAdmins'} };
	my $update=0;
	foreach my $admin ( @{ $ref_groups_v->{'GROUPS'}{$group}{'sophomorixAdmins'} }){
	    my $teachergroup;
	    if ($ref_AD_check->{'sAMAccountName'}{$admin}{'sophomorixSchoolname'} eq $DevelConf::name_default_school){
                $teachergroup=${DevelConf::teacher};
	    } else {
		$teachergroup=$ref_AD_check->{'sAMAccountName'}{$admin}{'sophomorixSchoolname'}."-".${DevelConf::teacher};
            }
            # test if admin is a former teacher
	    if ($ref_AD_check->{'sAMAccountName'}{$admin}{'sophomorixRole'} eq "student"){
                # admin is a student
		if ($ref_AD_check->{'sAMAccountName'}{$admin}{'sophomorixAdminClass'} ne $teachergroup and
                    $ref_AD_check->{'sAMAccountName'}{$admin}{'sophomorixExitAdminClass'} eq $teachergroup
		    ) {
		    # admin was in the teacher group
                    print "  $admin is an admin in $group, was a teacher but is not a teacher anymore\n";
		    $update=1;
                    @admins = &remove_from_list($admin,@admins);
		    @admins =sort @admins;
		}

	    } else {
		next;
	    }
	}
	if ($update==1){
	    my $admins=join(",",@admins);
	    print "    Old admins of $group: @{ $ref_groups_v->{'GROUPS'}{$group}{'sophomorixAdmins'} }\n";
	    print "    New admins of $group: $admins\n";
	    #print Dumper ($ref_groups_v->{'GROUPS'}{$group});
            &AD_group_update({ldap=>$ldap,
                              root_dse=>$root_dse,
                              dn=>$ref_groups_v->{'GROUPS'}{$group}{'DN'},
		              school=>$ref_groups_v->{'GROUPS'}{$group}{'sophomorixSchoolname'},
                              type=>$ref_groups_v->{'GROUPS'}{$group}{'sophomorixType'},
                              admins=>$admins,
                              sophomorix_config=>\%sophomorix_config,
                            });
	}
    }
    &print_title("END: Removing former teachers from classes");
}
