# coding=utf-8
# schukodaemon für MAusSK und linuxmuster.net ab Version 7.0
# programmiert von Martin Resch
#
#
# 1.00 erste Python - Version - alpha
# 1.01 + Einsammeln intern abgearbeitet
# Bug: CheckTeacherHost bei mehreren Login
# + WOLCommand mit Optionen
# + angepasst an python3 (String, Listen Konvertierungen)
# + besetzt nur bei classrooms
# + Option DisableTeacherSperre
# 1.04d
#

import mybase

Version = '1.04d'
Datum = 'April 2023'
lmn7 = True
mybase.printlog = False
######################################################################
import configparser
from mybase import *
from ServerThread import Socket_Start
import os, socket, grp, pwd, threading, platform
from threading import Thread
from time import sleep
######################################################################
mybase.Workstations_Pfad = Workstations_Pfad7
mybase.LastIP=''
mybase.LastRaum=''

######################################################################
# Testcode und dann beenden #
# print('>>>>>>>>>> Starte Testcode <<<<<<<<<<')
# mybase.debug=True
# mybase.UseLog=True
# mybase.printlog = True
# mybase.verbose=2
# CheckTeacherHost('schoen','10.32.100.3')
# CheckTeacherHostIP('schoen','10.32.100.3')
# print(platform.python_version())
# print('>>>>>>>>>> Beende Testcode <<<<<<<<<<')
# exit(0)
######################################################################

log = 'schukodaemon started - Version: '+ Version+' - python: '+platform.python_version();  logmessage(log)
if os.path.isfile(Config_Pfad):
    config = configparser.ConfigParser()
    config.read(Config_Pfad)
    mybase.SKPort      = config.getint(Programm_sec,Port_entry,fallback = DefSKPort)
    mybase.MDPort      = config.getint(Programm_sec, MDPort_entry, fallback = DefMDPort)
    ### SSL derzeit noch nicht implementiert
    #SSLPort     = config.getint(Programm_sec, SSLPort_entry, fallback = DefSSLPort)
    #UseSSL      = config.getboolean(Programm_sec, UseSSL_entry, fallback =False)
    #KeyFilePfad = config.get(Programm_sec, KeyFilePfad_entry, fallback =DefKeyFile_Pfad)
    #CAFilePfad  = config.get(Programm_sec, CAFilePfad_entry, fallback =DefCAFile_Pfad)
    mybase.UseLog = config.getboolean(Programm_sec, UseLog_entry, fallback=True)
    mybase.debug   = config.getboolean(Programm_sec, debug_entry, fallback = False)
    mybase.verbose = config.getint(Programm_sec, verbose_entry, fallback = 0)
    mybase.LogPfad     = config.get(Programm_sec, LogPfad_entry, fallback = DefLog_Pfad)
    mybase.AdminUsers  = config.get(Programm_sec, AdminUsers_entry, fallback = DefAdminUsers)
    mybase.DisableTeacherSperre = config.get(Programm_sec, DisableTeacherSperre_entry, fallback=True)
    mybase.MDPfad      = config.get(Programm_sec, MDPfad_entry, fallback = DefMD_Pfad7)
    mybase.MD5Pfad     = config.get(Programm_sec, MD5Pfad_entry, fallback = DefMD5_Pfad7)
    mybase.VNCTextPfad = config.get(Programm_sec, VNCTextPfad_entry, fallback = DefVNCText_Pfad7)
    mybase.VNCPassPfad = config.get(Programm_sec, VNCPassPfad_entry, fallback = DefVNCPass_Pfad7)
    mybase.UseAuth     = config.getint(Programm_sec, UseAuth_entry, fallback = 1)
    mybase.UseToken    = config.getboolean(Programm_sec, UseToken_entry, fallback = True)
    mybase.MDUseAuth   = config.getboolean(Programm_sec, MDUseAuth_entry, fallback = True)
    mybase.InternetSperreClients   = config.getboolean(Programm_sec, InternetSperreClients_entry, fallback = False)
    mybase.MenuePass   = config.getboolean(Programm_sec, MenuePass_entry, fallback = False)
    mybase.ResetTime   = config.getint(Programm_sec, ResetTime_entry, fallback = defResetTime)
    mybase.TokenTime   = config.getint(Programm_sec, TokenTime_entry, fallback = defTokenTime)
    mybase.BesetztTime = config.getint(Programm_sec, BesetztTime_entry, fallback=defBesetztTime)
    mybase.TerminateCollectTime   = config.getint(Programm_sec, TerminateCollectTime_entry, fallback = defTerminateCollectTime)
    mybase.TerminateTransferTime   = config.getint(Programm_sec, TerminateTransferTime_entry, fallback = defTerminateTransferTime)
    mybase.WolDelay    = config.getint(Programm_sec, WOLDelay_entry, fallback = 500)
    mybase.WolCommand  = config.get(Programm_sec, WOLCommand_entry, fallback = '')
    mybase.UseLineFeed = config.getboolean(Programm_sec, LF_entry, fallback = True)
    mybase.onlyCL      = config.getboolean(Programm_sec, onlyCL_entry, fallback = True)
    mybase.tzone       = config.getint(Programm_sec, timezone_entry, fallback = 2)
    mybase.UseTeacherPrefix = config.getboolean(Programm_sec, UseTeacherPrefix_entry, fallback=True)
    mybase.SchoolBasisPfad = config.get(Programm_sec, SchoolBasisPfad_entry, fallback = DefSchoolBasisPfad)
    mybase.Teacher_Pfad7  = mybase.SchoolBasisPfad + 'teachers/'
    mybase.Schueler_Pfad7 = mybase.SchoolBasisPfad + 'students/'
    mybase.UseSophomorix   = config.getboolean(Programm_sec, UseSophomorix_entry, fallback = DefUseSophomorix)
    mybase.AE_BasisPfad    = config.get(Programm_sec, AE_BasisPfad_entry, fallback = DefAE_BasisPfad)
    mybase.AusZuPfad   = config.get(Programm_sec, AusZuPfad_entry, fallback = DefAusZuPfad)
    mybase.AusGePfad   = config.get(Programm_sec, AusGePfad_entry, fallback = DefAusGePfad)
    mybase.EinGePfad   = config.get(Programm_sec, EinGePfad_entry, fallback = DefEinGePfad)
    mybase.EinZuPfad   = config.get(Programm_sec, EinZuPfad_entry, fallback = DefEinZuPfad)
    mybase.SocketTimeout_CL   = config.getint(Programm_sec, SocketTimeout_CL_entry, fallback = defSocketTimeout_CL)
    mybase.DelayTime   = config.getint(Programm_sec, DelayTime_entry, fallback = defDelayTime)
    mybase.TimerIntervall = config.getint(Programm_sec, TimerIntervall_entry, fallback = 60)
    log=('Konfig: '+Config_Pfad+' geladen.')
else:
    log=('ERROR: Konfig: ' + Config_Pfad + ' nicht gefunden')
logmessage(log)

s = ' verbose = ' + str(mybase.verbose) if (mybase.verbose > 0) else ''
debugmessage('Debug ist eingeschaltet ... ' + s)
mybase.useLF = lf if mybase.UseLineFeed else ''

def SKTimer(interval_sec):
    logmessage('SKTimer -> Start')
    while True:
        sleep(interval_sec)
        if mybase.StopSKTimer:
            logmessage('SKTimer-Thread -> Stop')
            break
        if (mybase.verbose > 1) :
            if len(mybase.ResetList) > 0:
                debugmessage('SKTimer -> Reset-Liste: \n  >> ' + '\n  >>'.join(mybase.ResetList))
            if len(mybase.KeyList) > 0:
                debugmessage('SKTimer -> GKEY-Liste:')
                for key,value in list(mybase.KeyList.items()):  # angepasst für python3 mit list()
                    debugmessage('  >> '+key+' : '+value)
            if len(mybase.TokenList) > 0:
                debugmessage('SKTimer -> Token-Liste: \n  >> ' + '\n  >> '.join(mybase.TokenList))
            else:
                debugmessage('SKTimer -> Run')
        for z in mybase.ResetList:
            Daten = z.split(';')
            Diff = datetime.now() - datetime.strptime(Daten[2],"%d/%m/%Y %H:%M:%S")
            # print (Diff.total_seconds())
            if (Diff.total_seconds() > mybase.ResetTime * 60) and (len(Daten) == 4) and (Daten[0] == 'RAUM'):
                Raum = Daten[1]
                UserList = Daten[3]
                debugmessage('Timer-Reset-RAUM: ' + Raum + ' ' + UserList)
                SetDefaultRaumStatus(Raum, UserList)
                mybase.ResetList.remove(z)
            elif (Diff.total_seconds() > mybase.ResetTime * 60) and (len(Daten) == 4) and (Daten[0] == 'WLAN'):
                if os.path.isfile(Soph_mana_script_Pfad):
                    UserList = Daten[3]
                    debugmessage('Timer-Reset-WLAN: ' + Daten[1]+' '+UserList)
                    Befehl = [Soph_mana_script_Pfad, '--nowifi', UserList]
                    Befehl_exec(Befehl)
                    mybase.ResetList.remove(z)
                else:
                    logmessage('FEHLER: Management-Script fehlt: ' + Soph_mana_script_Pfad)
                    mybase.ResetList.remove(z)
            elif (Diff.total_seconds() > mybase.ResetTime * 60) and (len(Daten) == 4) and (Daten[0] == 'PROCESS'):
                mybase.ResetList.remove(z)
            elif (Diff.total_seconds() > mybase.TerminateTransferTime * 60) and (len(Daten) == 4) and (Daten[0] == 'DATEI'):
                for thread in threading.enumerate():
                    if (str(thread.ident) == Daten[3]) :
                        debugmessage('TIMEOUT für Thread:' + thread.name + ' ident: ' + str(thread.ident))
                logmessage('SKTimer -> Dateiuebertragung abgebrochen ??? - timeout')
                mybase.ResetList.remove(z)
        for z in mybase.TokenList:
            Daten = z.split(';')
            if (len(Daten) == 4):
                Diff = datetime.now() - datetime.strptime(Daten[2], "%d/%m/%Y %H:%M:%S")
                if (Diff.total_seconds() > mybase.TokenTime * 60):
                    TokenList.remove(z)
                    if mybase.debug:
                        debugmessage('Token geloescht: ' + Daten[0] + ' (' + '\n'.join(mybase.TokenList) + ')')
                    else:
                        logmessage('Token geloescht: ' + Daten[0])

Zeitdauer = mybase.TimerIntervall  # Überprüfungsintervall in Sekunden
ResetDaemon = Thread(target=SKTimer, args=(Zeitdauer,))
ResetDaemon.start()

ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = ''

class MAusSKThread(Thread):
    def __init__(self,clientAddress,clientsocket, Con_Nr):
        Thread.__init__(self),
        self.Connection = clientsocket
        self.address = clientAddress
        self.Con_Nr = Con_Nr
        self.Con_State = 'Warte'
        self.FilePfad = ''
        self.Filesize = 0
        self.Benutzer = ''
        debugmessage ('****** init Connection '+ str(Con_Nr)+' : '+ clientAddress[0]+' ******')
    def run(self):
        debugmessage ('****** run Connection '+ str(self.Con_Nr)+' : '+ self.address[0]+' ******')
        while True:
            if (self.Con_State == 'Empfange_Datei') :
                self.Connection.settimeout(10)           # Timeout 10 Sekunden
                logmessage('***** Datei-Uebertragung ('+os.path.basename(self.FilePfad)+') -> Start *****')
                summe = 0
                try:
                    f = open(self.FilePfad,'wb')
                    # f = open('testfile002', 'wb')
                    Daten = self.Connection.recv(2048)
                    summe = len(Daten)
                    while Daten:
                        if mybase.verbose > 2:
                            debugmessage('..... Empfang von Datei - Bytes: '+str(summe)+ '('+str(self.Filesize)+')')
                        f.write(Daten)
                        Daten = self.Connection.recv(2048)
                        summe = summe + len(Daten)
                        if summe >= self.Filesize :
                            break
                except Exception as e:
                    logmessage('Dateiuebertragung: '+str(e))
                finally:
                    f.close()
                try:
                    uid = pwd.getpwnam(self.Benutzer).pw_uid
                    gid = grp.getgrnam('teachers').gr_gid
                    os.chown(self.FilePfad, uid, gid)
                except Exception as e:
                    logmessage('Dateiuebertragung: ' + str(e))
                if (summe == self.Filesize) :
                    if mybase.verbose > 1:
                        debugmessage('***** Dateipfad: ' + self.FilePfad + ' *****')
                    logmessage('***** Datei-Uebertragung OK -> Ende *****')
                    Connection.sendall(getAntwortJson(tag_ENDE))
                    TimerDel('DATEI',self.address[0])
                else:
                    debugmessage('ERROR: Datei unvollstaendig uebertragen: '+str(summe)+ '('+str(self.Filesize)+')')
                self.Con_State = 'Warte'
                self.Connection.settimeout(None)
            else:
                Daten = self.Connection.recv(1024)
                if Daten:
                    if verbose > 1:
                        debugmessage(Daten)
                    Socket_Start(self, self.Connection, Daten, self.address, self.Con_Nr)
                else:
                    # print ('keine weiteren Daten')
                    break
        debugmessage ('****** disconnect Connection '+ str(self.Con_Nr) + ' : '+self.address[0] + '  ******')

try:
    ServerSocket.bind((host,mybase.SKPort))
    log=('Listening to ' + str(mybase.SKPort));     logmessage(log)
    Connection_Nr = 0
    mybase.Thread_Nr = 0   # Threads für die MD-Clients
    while True:
        Connection_Nr += 1
        debugmessage('****** Connection ' + str(Connection_Nr) + ' Listen ******')
        ServerSocket.listen(1)
        Connection, address = ServerSocket.accept()
        newthread = MAusSKThread(address, Connection, Connection_Nr)
        newthread.start()
except socket.error as e:
    logmessage(str(e))
finally:
    mybase.StopSKTimer = True
    print ('Exit schukodaemon')



