#!/usr/bin/env python
# Fabien Priotto UM/FDS/SIF - 2015
# Receive job orders from netupdatedb service and send the job status message to netupdatedb service
# -*- coding: UTF8 -*- 

import socket
import threading
import os # operate system calls
import sys # System constants
import locale # Environnement encoding
import subprocess # spawn new processes, connect to their input/output/error pipes, and obtain their return codes
import codecs # convert to unicode file names
import logging
import errno
import string
import random
import signal
import daemon # PEP 3143 -- Standard daemon process library

encoding = sys.getfilesystemencoding()

from socket import error as socket_error
from subprocess import call

context = daemon.DaemonContext(
    working_directory='/tmp',
    umask=0o002,
)

context.signal_map = {
    signal.SIGTERM: 'terminate',
    signal.SIGHUP: 'terminate',
    signal.SIGUSR1: 'terminate'
}


from socket import error as socket_error
from subprocess import call

context.open()

### Some vars to set ###
JOBS_DIR = '/usr/local/netupdate'
PASSWORD='3RSvTqcmyrJZMaj26io90bxMVS9dv8KD' # Comment this line out and uncomment the line below to use a random value at start
#PASSWORD = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)]) # generate the random passphrase
LISTENING_PORT=1111
AUTH_OK_FLAG='OK'
AUTH_FAILED_FLAG='AuthFailed'
MAX_CLIENTS_NUMBER=5 #the maximum number of queued connections 
###


class ClientThread(threading.Thread):

	def __init__(self, ip, port, clientsocket, passph=""):

		threading.Thread.__init__(self)
		self.ip = ip
		self.port = port
		self.clientsocket = clientsocket
		self.passph = passph

	def run(self): 
		logging.info('New thread from %s %s',self.ip, self.port)
	
		try:
			r = self.clientsocket.recv(2048) # Receiving the passphrase from client
			logging.info('Connection from %s:%s sending passphrase %s', self.ip, self.port, r)

			if r == self.passph:
				self.clientsocket.send(AUTH_OK_FLAG)
		
				try:
					r = self.clientsocket.recv(2048)
	
					job = os.path.join(JOBS_DIR, r)
					logging.info('Trying to start the job %s', job)

					try:
						retcode = subprocess.call(job, shell=True) # Starting the job...
						logging.info('Sending log data: task=%s , return code=%s', job, retcode)
						self.clientsocket.send(str(retcode))
	
					except subprocess.CalledProcessError as e:
						logging.error('The job %s failed with error %s', job, e)

					except OSError:
						logging.error('Job %s file not found', job)
					exit()	
				except socket_error as serr:
					logging.error('Job %s: socket error while receving: %s', job_id, serr.errno)
					exit()			
			else:
				self.clientsocket.send(str(AUTH_FAILED_FLAG))
				exit('ERROR: authentication failed !')
	
		except socket_error as serr:
			logging.error('Job %s: socket error while receving: %s', job_id, serr.errno)
			exit()						

with context:
### MAIN ###
    tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        tcpsock.bind(("",LISTENING_PORT))
    except socket.error:
        print u'Can not listen on', LISTENING_PORT,'!'
        exit('Bye')


    while 1:
        try:
            TRACE_FILE = '/var/log/netupdate/netupdate.log' # Local file for logging output.
            fp = open(TRACE_FILE, 'a').close()
            logging.basicConfig(filename=TRACE_FILE,level=logging.DEBUG,\
            format='%(asctime)s -- %(pathname)s[%(process)d]: -- %(levelname)s -- %(message)s')

        except IOError:
            print u'ERROR: can not open file', TRACE_FILE, '!'
            exit("Bye")
        try:
            logging.info('Listening with password %s on port %s',PASSWORD,LISTENING_PORT)
            tcpsock.listen(MAX_CLIENTS_NUMBER)
            (clientsocket, (ip, port)) = tcpsock.accept()
            newthread = ClientThread(ip, port, clientsocket, PASSWORD)
            newthread.start()

        except  Exception, e:			
            logging.error(e)
            exit()

        except KeyboardInterrupt:
            logging.info('Stopped by user')
            exit('Stopped by user')	

context.close()
