#!/usr/bin/python
# coding=UTF-8

# iTalc master launcher using avahi
# Written by Stéphane Graber <stgraber@ubuntu.com>

from xml.dom import minidom
from hashlib import md5
import subprocess, re, socket, os, sys, urllib2, gettext, pwd

# gettext
gettext.textdomain('italc')
_ = gettext.gettext

# Store the old environment
lang=os.environ["LANG"]

# Empty config file to use if it doesn't already exist
skeleton="""<?xml version="1.0"?>
<!DOCTYPE italc-config-file>
<globalclientconfig version="1.0.9" >
  <body>
  </body>
</globalclientconfig>"""

try:
    confdir=os.environ.get("HOME")+"/.italc/"
except:
    sys.exit('Invalid or undefined env variable \"HOME\"')

access="none"

try:
    file=open("/etc/italc/keys/public/teacher/key","r")
    md5_1=md5(file.read()).hexdigest()
    file.close()
    access="TEACHERROLENAME"
except:
    md5_1="0"

try:
    file=open("/etc/italc/keys/public/admin/key","r")
    md5_2=md5(file.read()).hexdigest()
    file.close()
    if (acces == "none"):
        access="ADMINROLENAME"
except:
    md5_2="0"

try:
    file=open("/etc/italc/keys/public/supporter/key","r")
    md5_3=md5(file.read()).hexdigest()
    file.close()
    if (acces == "none"):
        access="SUPPORTERROLENAME"
except:
    md5_3="0"

if (access == "none"):
    sys.exit('iTalc keys not correctly installed')

def getLocalIPs_ip(path=""):
    "Scan ip addr output for local IPV4 addresses"
    os.environ["LANG"]="C" # Set environ LANG to C
    ip=[]
    output=subprocess.Popen([path+"ip", "addr", "show"],stdout=subprocess.PIPE)
    output.wait()
    for line in output.stdout.readlines():
        line=line.strip()
        if line.startswith("inet "):
            ip.append(line.split(" ")[1].split("/")[0])
    return ip

def getLocalIPs_ifconfig(path=""):
    "Scan ifconfig output for local IPV4 addresses"
    os.environ["LANG"]="C" # Set environ LANG to C
    ip=[]
    output=subprocess.Popen(path+"ifconfig",stdout=subprocess.PIPE)
    output.wait()
    for line in output.stdout.readlines():
        line=line.strip()
        if line.startswith("inet addr"):
            ip.append(line.split(" ")[1].split(":")[1])
    return ip

def getLocalIPs():
    "Output for local IPV4 addresses"
    for f in [getLocalIPs_ip, getLocalIPs_ifconfig]:
        for path in ["", "/sbin/", "/usr/sbin/"]:
            try:
                ip=f(path)
		return ip
            except:
                ip=[]
    return ip

def getHostPort():
    isdhost="127.0.0.1"
    try:
        output=subprocess.Popen(["xprop", "-root", "ICA_PORT"], stdout=subprocess.PIPE).communicate()[0]
        isdport=str(int(output.split('=')[1].strip()))
    except:
        isdport="5800"

    if "LTSP_CLIENT" in os.environ:
        xprop=subprocess.Popen(["xprop","-root","ica_ltsp"],stdout=subprocess.PIPE)
        xprop.wait()
        if xprop.stdout.read().split(" ")[2].strip() == "1":
            isdhost=os.environ["LTSP_CLIENT"]
        else:
            isdport=str(int(os.environ["LTSP_CLIENT"].split(".")[3])+11000)
    return [isdhost,isdport]

def getClusterHosts():
    hosts=[]
    if not os.path.exists("/etc/ltsp/getltscfg-cluster.conf") or not "LTSP_CLIENT" in os.environ:
        return hosts
    conf=open("/etc/ltsp/getltscfg-cluster.conf","r")
    for line in conf.readlines():
        if line.startswith("SERVER="):
            controlcenter=line.replace("SERVER=","").strip()
    conf.close()
    if not controlcenter:
        return hosts
    page=urllib2.urlopen("http://"+controlcenter+"/ltsp-cluster-control/Terminal/TeachTools.php?list/ip="+os.environ["LTSP_CLIENT"])
    for line in page.readlines():
        line=line.strip()
        if line and not line.startswith("CLASSROOM"):
            ip,mac,username=line.split(",")
            try:
                gecos=pwd.getpwnam(username).pw_gecos
            except:
                gecos=username
            hosts.append([ip+":5900",0,gecos,mac])
    return hosts

def getAvahiHosts():
    global local_addr
    hosts=[]
    if not os.path.exists("/usr/bin/avahi-browse"):
        return hosts
    client_list=subprocess.Popen(["avahi-browse","-trp","_italc._tcp"],stdout=subprocess.PIPE)
    client_list.wait()
    for line in client_list.stdout.readlines():
        if line.startswith("="):
            try:
                param=line.split(";")
                comment=re.findall('"(.*)" "(.*)" "(.*)" "(.*)"\n',param[9])[0]
                if (comment[1] == md5_1 or comment[2] == md5_2 or comment[3] == md5_3):
                    if param[7] in local_addr:
                        param[7]="127.0.0.1"
                    host=[param[7]+":"+param[8],1,comment[0]]
                    if host not in hosts:
                        hosts.append(host)
            except:
                print 'Ignoring a client, invalid data received'
    return hosts

def getMAC(host):
    "Scan arp output for get host MAC addresses"
    os.environ["LANG"]="C" # Set environ LANG to C
    for path in ["", "/sbin/", "/usr/sbin/"]:
        try:
            output=subprocess.Popen((path+"arp", host, "-n", "-a"),stdout=subprocess.PIPE)
            output.wait()
            mac=output.stdout.read().strip().split(" ")[3]
            if not re.match("^..:..:..:..:..:..$",mac):
                mac=""
            return mac
        except:
            mac=""
    return mac

local_addr=getLocalIPs()
isdhost,isdport=getHostPort()

# Whether to use avahi autodetection. Defaults to true
autodetect_clients=not subprocess.Popen(["sh", "-c", ". /etc/italc/italc.conf >/dev/null 2>&1 && echo $AUTODETECT_CLIENTS"], stdout=subprocess.PIPE).communicate()[0].strip().upper() in ["FALSE", "F", "0"]

if autodetect_clients:
    try:
        xmldoc=minidom.parse(confdir+"globalconfig.xml")
        body=xmldoc.getElementsByTagName("globalclientconfig")[0].getElementsByTagName("body")[0]
        classrooms=body.getElementsByTagName("classroom")
    except:
        mkdir=subprocess.Popen(["mkdir","-p",confdir])
        mkdir.wait()
        try:
            config=open(confdir+"globalconfig.xml","w+")
            config.write(skeleton)
            config.close()
        except:
            sys.exit('Unable to write to config file')
        xmldoc=minidom.parse(confdir+"globalconfig.xml")
        body=xmldoc.getElementsByTagName("globalclientconfig")[0].getElementsByTagName("body")[0]
        classrooms=body.getElementsByTagName("classroom")

    # Scan for an existing classroom and delete it
    for classroom in classrooms:
        if classroom.getAttribute("name") == _("Auto-detected computers"):
            body.removeChild(classroom)

    # Create the Auto-detected computers classroom
    avahi=xmldoc.createElement("classroom")
    avahi.setAttribute("name",_("Auto-detected computers"))
    avahi.setAttribute("forcevisible","yes")
    body.appendChild(avahi)

    # Add computers to the classroom
    clients=sorted(getAvahiHosts()+getClusterHosts(), reverse=True)
    count=0

    if isdhost not in local_addr:
        local_addr=[isdhost]

    for client in clients:
        try:
            line=client[0]
            host,port=line.split(":")
            if (host in local_addr and str(int(isdport)+100) == port):
                continue

            # Do not perform some checks when in ltsp-cluster environment
            if client[1] == 1:
                # Make sure we have a running VNC server
                connection=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                connection.connect((host,int(port)))
                connection.close()

                # Get MAC address
		mac=getMAC(host)
            else:
                mac=client[3]

            # Generate the name
            if client[2] != '':
                name=client[2]
            else:
                name="client "+str(count)

            # Generate the new node
            client=xmldoc.createElement("client")
            client.setAttribute("id",str(count))
            client.setAttribute("localip",host+":"+port)
            client.setAttribute("mac",mac)
            client.setAttribute("name",name)
            client.setAttribute("type","0")
            avahi.appendChild(client)
            count+=1
        except:
            print 'Ignoring a client, invalid data received'

    try:
        file=open(confdir+"globalconfig.xml","w")
        file.write(xmldoc.documentElement.toxml( "utf-8"))
        file.close()
    except:
        exit('Failed to save updated config')

print "Starting italc as "+access+" ("+isdhost+":"+isdport+")"

# Restore environment
os.environ["LANG"]=lang

subprocess.Popen(["italc","-isdport",isdport,"-isdhost",isdhost,"-role",access])
