#!/usr/bin/perl -w
# This script (sophomorix-managementgroup) 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 List::MoreUtils qw(uniq);
use Net::LDAP;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Useqq = 1;
$Data::Dumper::Terse = 1; 
use JSON;
use Sophomorix::SophomorixBase qw(
                                 print_line
                                 print_title
                                 log_script_start
                                 log_script_end
                                 log_script_exit
                                 check_options
                                 config_sophomorix_read
                                 result_sophomorix_init
                                 result_sophomorix_add
                                 result_sophomorix_check_exit
                                 result_sophomorix_print
                                 result_sophomorix_add_log
                                 json_dump
                                 remove_embracing_whitespace
                                 create_schoollist
                                 );
use Sophomorix::SophomorixSambaAD qw(
                                 AD_school_create
                                 AD_bind_admin
                                 AD_dns_get
                                 AD_unbind_admin
                                 AD_get_name_tokened
                                 AD_group_addmember
                                 AD_group_addmember_management
                                 AD_group_removemember
                                 AD_get_groups_v
                                 AD_get_full_groupdata
                                 AD_get_AD_for_check
                                 AD_debug_logdump
                                 AD_get_group
                                 AD_get_passwd
                                 AD_rolegroup_update
                                    );

my @arguments = @ARGV;

# ===========================================================================
# Variablen
# ==========================================================================

my $help=0;
my $info=0;
my $json=0;

# option vars
$Conf::log_level=1;
my $school="---";
my $managementgroup="";

my $internet="";
my $intranet="";
my $wifi="";
my $webfilter="";
my $printing="";
my $admins="";

my $nointernet="";
my $nointranet="";
my $nowifi="";
my $nowebfilter="";
my $noprinting="";
my $noadmins="";

my $set_internet="";
my $set_wifi="";
my $set_intranet="";
my $set_webfilter="";
my $set_printing="";

my $set_group="";
my $set_name="";

my $update_rolegroups=0;

my $skiplock=0;

# Parsen der Optionen
my $testopt=GetOptions(
           "help|h" => \$help,
           "info|i" => \$info,
           "json|j+" => \$json,
           "verbose|v+" => \$Conf::log_level,
           "skiplock" => \$skiplock,
           "managementgroup|m=s" => \$managementgroup,
           "internet=s" => \$internet,
           "intranet=s" => \$intranet,
           "wifi=s" => \$wifi,
           "webfilter=s" => \$webfilter,
           "printing=s" => \$printing,
           "admins=s" => \$admins,
           "nointernet=s" => \$nointernet,
           "nointranet=s" => \$nointranet,
           "nowifi=s" => \$nowifi,
           "nowebfilter=s" => \$nowebfilter,
           "noprinting=s" => \$noprinting,
           "noadmins=s" => \$noadmins,
           "school=s" => \$school,
           "set-internet=s" => \$set_internet,
           "set-wifi=s" => \$set_wifi,
           "set-intranet=s" => \$set_intranet,
           "set-webfilter=s" => \$set_webfilter,
           "set-printing=s" => \$set_printing,
           "set-group=s" => \$set_group,
           "set-name=s" => \$set_name,
           "update-rolegroups" => \$update_rolegroups,
          );

my %sophomorix_result=&result_sophomorix_init("sophomorix-managementgroup");
# Prüfen, ob Optionen erkannt wurden, sonst Abbruch
&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);

# --help
if ($help==1) {
   # Scriptname ermitteln
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   # Befehlsbeschreibung
   print('
sophomorix-managementgroup adds/removes a user from a managementgroup

Options:
  -h  / --help
  -v  / --verbose
  -vv / --verbose --verbose
  -i  / --info
  -i -m <groupname> / --info --managementgroup <groupname>

Examples: 
Add/Remove users from a managementgroup:
  sophomorix-managementgroup  --internet  <user1,user2,...>   --nointernet  <user1,user2,...>
                              --wifi      <user1,user2,...>   --nowifi  <user1,user2,...>
                              --intranet  <user1,user2,...>   --nointranet  <user1,user2,...> 
                              --webfilter <user1,user2,...>   --nowebfilter <user1,user2,...> 
                              --printing  <user1,user2,...>   --noprinting  <user1,user2,...>
                              --admins    <user1,user2,...>   --noadmins <user1,user2,...>

Set specific managementgroup memberships from a file:
  sophomorix-managementgroup --set-internet <name> --school <school>
  sophomorix-managementgroup --set-wifi <name> --school <school>
  sophomorix-managementgroup --set-intranet <name> --school <school>
  sophomorix-managementgroup --set-webfilter <name> --school <school>
  sophomorix-managementgroup --set-printing <name> --school <school>

Example for group wifi:
  A) Create and edit:
     /etc/linuxmuster/sophomorix/<schoolname>/[<schoolname.>]wifi.<name>.conf
  B) Specify members according to:
      /usr/share/sophomorix/devel/master/group.default.conf.master
  C) Apply configured membeships:
     # sophomorix-managementgroup --set-wifi <name> --school <school>

Set any sophomorix-group memberships from a file (i.e. epoptes):
  A) create the group with 
     # sophomorix-group --create --group epoptes --school global
  B) Specify members according to:
      /usr/share/sophomorix/devel/master/group.default.conf.master
  C) Apply configured membeships:
     # sophomorix-managementgroup --set-group <group> --set-name <name> --school <school>
     Example for global epoptes group:
     # sophomorix-managementgroup --set-group epoptes --set-name default --school global
     (This will read the config in /etc/linuxmuster/sophomorix/global.epoptes.default.conf)

Set GLOBAL rolegroup memberships:
  --update-rolegroups

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

# --info
if ($info==1 and $managementgroup eq ""){
    my $ref_group_v=&AD_get_groups_v({ldap=>$ldap,
                                   root_dse=>$root_dse,
                                   root_dns=>$root_dns,
                                   school=>$school,
                                   sophomorix_config=>\%sophomorix_config,  
                                  });
    #print Dumper($ref_group_v);
    my $jsoninfo="MANAGEMENTGROUPS_OVERVIEW";
    my $jsoncomment="Managementgroup";
    &json_dump({json => $json,
                jsoninfo => $jsoninfo,
                jsoncomment => $jsoncomment,
                object_name => $school,
                log_level => $Conf::log_level,
                hash_ref => $ref_group_v,
                sophomorix_config => \%sophomorix_config,
               });
    exit;
}



if ($info==1 and $managementgroup ne ""){
    my $ref_groups=&AD_get_full_groupdata({ldap=>$ldap,
                                           root_dse=>$root_dse,
                                           root_dns=>$root_dns,
                                           grouplist=>$managementgroup,
                                           sophomorix_config=>\%sophomorix_config,
                                      });
    my $jsoninfo="MANAGEMENTGROUP";
    my $jsoncomment="Managementgroup";
    &json_dump({json => $json,
                jsoninfo => $jsoninfo,
                jsoncomment => $jsoncomment,
                object_name => $school,
                log_level => $Conf::log_level,
                hash_ref => $ref_groups,
                sophomorix_config => \%sophomorix_config,
               });
    exit;
}



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


############################################################
# add
############################################################

# --internet
if ($internet ne ""){
    &manage_groupmemberships("internet","add",$internet);
}


# --intranet
if ($intranet ne ""){
    &manage_groupmemberships("intranet","add",$intranet);
}


# --wifi
if ($wifi ne ""){
    &manage_groupmemberships("wifi","add",$wifi);
}


# --webfilter
if ($webfilter ne ""){
    &manage_groupmemberships("webfilter","add",$webfilter);
}


# --printing
if ($printing ne ""){
    &manage_groupmemberships("printing","add",$printing);
}


# --admins
if ($admins ne ""){
    &manage_groupmemberships("admins","add",$admins);
}






############################################################
# remove
############################################################

# --nointernet
if ($nointernet ne ""){
    &manage_groupmemberships("internet","remove",$nointernet);
}


# --nointranet
if ($nointranet ne ""){
    &manage_groupmemberships("intranet","remove",$nointranet);
}


# --nowifi
if ($nowifi ne ""){
    &manage_groupmemberships("wifi","remove",$nowifi);
}


# --nowebfilter
if ($nowebfilter ne ""){
    &manage_groupmemberships("webfilter","remove",$nowebfilter);
}


# --noprinting
if ($noprinting ne ""){
    &manage_groupmemberships("printing","remove",$noprinting);
}


# --noadmins
if ($noadmins ne ""){
    &manage_groupmemberships("admins","remove",$noadmins);
}


############################################################
# set
############################################################
if ($school eq "---"){
    $school=$DevelConf::name_default_school;
}



# --set-internet <name>
if ($set_internet ne ""){
    &set_members_from_file("internet",$set_internet,$school);
}

# --set-wifi <name>
if ($set_wifi ne ""){
    &set_members_from_file("wifi",$set_wifi,$school);
}

# --set-intranet <name>
if ($set_intranet ne ""){
    &set_members_from_file("intranet",$set_intranet,$school);
}

# --set-webfilter <name>
if ($set_webfilter ne ""){
    &set_members_from_file("webfilter",$set_webfilter,$school);
}

# --set-printing <name>
if ($set_printing ne ""){
    &set_members_from_file("printing",$set_printing,$school);
}



# --set-group <group>
# --set-name <confname>
if ($set_group ne "" and $set_name ne ""){
    &set_members_from_file($set_group,$set_name,$school);
}


#  --update-rolegroups
if ($update_rolegroups==1){
    my ($smb_admin_pass)=&AD_get_passwd($DevelConf::sophomorix_file_admin,
                                         $DevelConf::secret_file_sophomorix_file_admin);
    # create new school in AD
    &AD_school_create({ldap=>$ldap,
                       root_dse=>$root_dse,
                       root_dns=>$root_dns,
                       school=>$DevelConf::name_default_school,
                       smb_admin_pass=>$smb_admin_pass,
                       sophomorix_config=>\%sophomorix_config,
                       sophomorix_result=>\%sophomorix_result,
                     });
    &AD_rolegroup_update($ldap,$root_dse,$root_dns,\%sophomorix_config);
}



&AD_unbind_admin($ldap);

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


############################################################
# sub
############################################################

sub set_members_from_file {
    my ($type,$name,$school)=@_;

    my $file_path;
    if ($school eq $DevelConf::name_default_school){
        $file_path=$DevelConf::path_conf_sophomorix."/".$school."/".$type.".".$name.".conf";
    } elsif ($school eq "global"){
        $file_path=$DevelConf::path_conf_sophomorix."/global.".$type.".".$name.".conf";
    } else {
        $file_path=$DevelConf::path_conf_sophomorix."/".$school."/".$school.".".$type.".".$name.".conf";
    }

    print "Configuring $type\n";
    if (not -e $file_path){
        print "\nERROR: $file_path nonexisting\n\n";
        exit 88;
    } else {
        print "   Reading $file_path\n";
        my %config=(); # save config from file
        my @new_member=(); # for update
        open(CONFIG, "<$file_path");
        while(<CONFIG>){
            my $line=$_;
            chomp($line);
            my ($opt,$arguments)=split(/=/,$line);
            $opt=&remove_embracing_whitespace($opt);
            my @args=split(/,/,$arguments);
            foreach my $arg (@args){
                $arg=&remove_embracing_whitespace($arg);
                $config{$opt}{$arg}="seen";
            }
        }
        close(CONFIG);
        # walk through all users
        my ($ref_AD_check) = &AD_get_AD_for_check({ldap=>$ldap,
                                                   root_dse=>$root_dse,
                                                   root_dns=>$root_dns,
                                                   admins=>"TRUE",
                                                   sophomorix_config=>\%sophomorix_config,
                                                 });
        # warn if class does not exist in this school
        foreach my $class (keys %{ $config{'MEMBER_CLASS'} }){
            if (not exists $ref_AD_check->{'LOOKUP_by_school'}{$school}{'group_BY_sAMAccountName'}{$class}){
                &result_sophomorix_add_log(\%sophomorix_result,
                    "WARNING: Configured MEMBER_CLASS $class does not exist in school $school");
            }
	}
        # warn if user does not exist in this school
        foreach my $user (keys %{ $config{'MEMBER_USER'} }){
            if (not exists $ref_AD_check->{'LOOKUP_by_school'}{$school}{'user_BY_sAMAccountName'}{$user}){
                &result_sophomorix_add_log(\%sophomorix_result,
                    "WARNING: Configured MEMBER_USER $user does not exist in school $school");
            }
	}
	#print Dumper($ref_AD_check);

        my $ref_group_v=&AD_get_groups_v({ldap=>$ldap,
                                          root_dse=>$root_dse,
                                          root_dns=>$root_dns,
                                          school=>$school,
                                          sophomorix_config=>\%sophomorix_config,
                                        });
        # warn if configured project is not a project does not exist in this school
        foreach my $project (keys %{ $config{'MEMBER_PROJECT'} }){
	    # configured name must exist
            if (not exists $ref_group_v->{'GROUPS'}{$project}
	       ){
                &result_sophomorix_add_log(\%sophomorix_result,
					   "WARNING: Configured MEMBER_PROJECT $project does not exist");
		next;
            }
	    # configured name must be a project
            if ($ref_group_v->{'GROUPS'}{$project}{'sophomorixType'} ne "project"
	       ){
                &result_sophomorix_add_log(\%sophomorix_result,
					   "WARNING: Configured MEMBER_PROJECT $project is not a project group");
		next;
            }
	    # configured name must be a project in school
            if ($ref_group_v->{'GROUPS'}{$project}{'sophomorixSchoolname'} ne $school
	       ){
                &result_sophomorix_add_log(\%sophomorix_result,
					   "WARNING: Configured MEMBER_PROJECT $project is not in school $school");
		next;
            }
	    # add the sAMAccountName of project student members of school to config
	    foreach my $dn (@{ $ref_group_v->{'GROUPS'}{$project}{'member'}{'student'} }){
		my @parts=split(",",$dn);
                my ($unused,$sam)=split("=",$parts[0]);
		$sam=~tr/A-Z/a-z/;
		# add users only if they are in the same school as project
                if (exists $ref_AD_check->{'LOOKUP_by_school'}{$school}{'user_BY_sAMAccountName'}{$sam}){
		    $config{'PROJECT_MEMBER_STUDENT_USER_SCHOOL'}{$sam}=$dn;
		}
	    }
	}
        # print Dumper($ref_group_v);
        # print Dumper(\%config);

        foreach my $sam (keys %{ $ref_AD_check->{'sAMAccountName'} }){
            if ($school eq "global"){
                # just go on
            } elsif (not $school eq $ref_AD_check->{'sAMAccountName'}{$sam}{'sophomorixSchoolname'}){
                # skip this user of another school
                next;
            }
            # role
            if (exists $config{'MEMBER_ROLE'}{$ref_AD_check->{'sAMAccountName'}{$sam}{'sophomorixRole'}}){
                push @new_member,$ref_AD_check->{'sAMAccountName'}{$sam}{'dn'};
            }
            # class
            if (exists $config{'MEMBER_CLASS'}{$ref_AD_check->{'sAMAccountName'}{$sam}{'sophomorixAdminClass'}}){
                push @new_member,$ref_AD_check->{'sAMAccountName'}{$sam}{'dn'};
            }
            # user
            if (exists $config{'MEMBER_USER'}{$sam}){
                push @new_member,$ref_AD_check->{'sAMAccountName'}{$sam}{'dn'};
            }
            # project users
            if (exists $config{'PROJECT_MEMBER_STUDENT_USER_SCHOOL'}{$sam}){
                push @new_member,$ref_AD_check->{'sAMAccountName'}{$sam}{'dn'};
            }
        }

        @new_member = uniq(@new_member);

        my $groupname;
        if ($school eq $DevelConf::name_default_school or 
            $school eq "global"
           ){
	    $groupname=$type;
        } else {
	    $groupname=$school."-".$type;
        }

        my ($existing_group,
            $type_group,
            $school_group,
            $status,
            $description,
            $dn)=
            &AD_get_group({ldap=>$ldap,
                          root_dse=>$root_dse,
                          root_dns=>$root_dns,
                          group=>$groupname,
                        });

        print "Setting member attribute of managementgroup\n";
        print "  $dn\n";
        print "to:\n";
        print Dumper (\@new_member);
        my $member_count=$#new_member+1;
        print "$member_count members in config \"$name\" for managemengroup \"$type\" in school \"$school\"\n";
        print "\n";

        my $mesg = $ldap->modify( $dn,
                          replace => { 'member' => \@new_member } 
                         );
        &AD_debug_logdump($mesg,2,(caller(0))[3]);
    }
}



sub manage_groupmemberships {
    my ($basegroup,$action,$groupstring)=@_;
    my @users=split(/,/,$groupstring);
    foreach my $user (@users){
        my $filter="(&(objectclass=user) (name=".$user."))";
        my $mesg = $ldap->search(
                          base   => $root_dse,
                          scope => 'sub',
                          filter => $filter,
                          attr => ['cn','sophomorixSchoolname']
                            );

        my $count = $mesg->count;
        if ($count==0){
            &log_script_exit("ERROR: Could not find user $user",1,1,0,\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
        }
        my $entry = $mesg->entry(0);
        my $school = $entry->get_value('sophomorixSchoolname');
        print "USER: $user is in school $school\n";
        
        my $group=&AD_get_name_tokened($basegroup,$school,"management");
        print "      group is $group\n";
	if ($action eq "remove"){
            print "      $user --> $group ($action)\n";

            &AD_group_removemember({ldap => $ldap, 
                                    root_dse => $root_dse, 
                                    group => $group,
                                    removemember => $user,
                                  });   

        } elsif ($action eq "add"){
            print "      $user --> $group ($action)\n";
 
            &AD_group_addmember_management({ldap => $ldap, 
                                            root_dse => $root_dse, 
                                            group => $group,
                                            addmember => $user,
                                           });  
        }
   }
}
