#!/usr/bin/env python3

# Author: Bandie Canis
# License: 2-Clause BSD license

import ssl, socket, subprocess, time, os, sys
import configparser

CONFIG = None
host = None
port = 0
cafile = None
certfile = None
keyfile = None
pw_on = None
password = None
pwtimeout = 30
tmppw_on = None
context = None
bindsocket = None



def getTimestamp():
  t = "[" + time.strftime("%Y-%m-%d %H:%M:%S") + "]"
  return t

def execFromConfig(option, pw=False):
  cfg = configparser.SafeConfigParser()
  cfg.read(CONFIG)

  if(pw):
    if(option == password):
      return 4
    else:
      return 5 

  else:

    try:
      cmd = cfg.get("Commands", option).replace("\"", "").replace("\'", "")
      cmd = cmd.split(" ")
      try:
        subprocess.Popen(cmd)
        return 0

      except FileNotFoundError:
        print(getTimestamp(), "Can't execute", cmd, ". File not found.")
        return 2

    except configparser.NoOptionError:
      print(getTimestamp(), "No execution set:", option)
      return 1


  
def main():
  while True:
    newsocket, fromaddr = bindsocket.accept()
    try:
      connstream = context.wrap_socket(newsocket, server_side=True) 
      print(getTimestamp(), "Incoming connection:", fromaddr[0])
      connstream.send(b"OK 1337\n")
      
      con_loop = True
      while con_loop:
        global tmppw_on, pw_on, pwtimeout
        if('timeout' in locals() and timeout<time.time()):
          del timeout
          tmppw_on=pw_on
          print(getTimestamp(), "Locked.")
        
        try:
          buf = connstream.recv(1024)
          if not buf: break
          buf = buf.decode("utf-8")
        except ssl.SSLEOFError:
          print(getTimestamp(), "SSL-EOF-Error.")
          con_loop = False
        except ConnectionResetError:
          print(getTimestamp(), "Connection reset.")
          serve()
        

               
        if(tmppw_on):
          retval = execFromConfig(buf, True)
          if(retval == 5):
            print(getTimestamp(), " ", fromaddr[0], ": Wrong Password.", sep="")
            connstream.send(b"ERR PW")
          if(retval == 4):
            print(getTimestamp(), " ", fromaddr[0], ": Unlocked for ", pwtimeout, "sec.", sep="")
            pwokstr = "OK PW " + str(pwtimeout)
            connstream.send(bytes(pwokstr, "utf-8"))
            timeout=time.time() + pwtimeout
            tmppw_on = False
           
        else:
          print(getTimestamp(), " ", fromaddr[0], ": ", buf, sep="")
          retval = execFromConfig(buf)
          if(retval == 0):
            connstream.send(b"OK CMD")
          elif(retval == 1):
            connstream.send(b"ERR NO_CMD")
          elif(retval == 2):
            connstream.send(b"ERR CMD_ERR")
        

    except ssl.SSLError as e:
      print(getTimestamp(), e)

    except EOFError:
      print(getTimestamp(), "EOF")

def init():

  global CONFIG, host, port, cafile, certfile, keyfile, pw_on, password, pwtimeout, tmppw_on, context, bindsocket
  
  if(os.name == 'nt'):
    CONFIG = "dingd.win.cfg"
  else:
    CONFIG = "dingd.cfg"

  cfg = configparser.SafeConfigParser()
  cfg.read(CONFIG)

  try:
    host=cfg.get("Server", "host").replace("\"","").replace("\'","")
    port=int(cfg.get("Server", "port").replace("\"","").replace("\'",""))
    cafile=cfg.get("Security", "cafile").replace("\"","").replace("\'","")
    certfile=cfg.get("Security", "certfile").replace("\"","").replace("\'","")
    keyfile=cfg.get("Security", "keyfile").replace("\"","").replace("\'","")
    pw_on=cfg.get("Security", "pw_on").replace("\"","").replace("\'","")
    password=cfg.get("Security", "password").replace("\"","").replace("\'","")
    pwtimeout=int(cfg.get("Security", "pwtimeout").replace("\"","").replace("\'",""))
    if(pw_on.upper() == "TRUE"):
      pw_on = True
    else:
      pw_on = False
    tmppw_on=pw_on
  except configparser.NoSectionError as e:
    print("Error in configuration file:", e, file=sys.stderr)
    quit(1)

  try:
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=cafile)
    context.load_cert_chain(certfile=certfile, keyfile=keyfile)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations(cafile=cafile)

  except FileNotFoundError as e:
    print(e)
    print("Please check your paths in the config file. (Have you forgotten to generate the Certificates?)")
    quit(2)

  try:
    if(":" in host):
      bindsocket = socket.socket(family=socket.AF_INET6)
    else:
      bindsocket = socket.socket(family=socket.AF_INET)
    bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    bindsocket.bind((host, port))
    bindsocket.listen(5)
  except socket.gaierror:
    print("Error: Hostname error. Name or service not known.")
    quit(1)
  except PermissionError:
    print("Error: Can't bind for port number ", port, ". Permission denied.", sep="")
    quit(1)
 
  print("Running dingd on ", host, ":", port,
        "\nConfig: ", CONFIG,
        "\nCAFile: ", cafile,
        "\nCertfile: ", certfile,
        "\nKeyfile: ", keyfile,
        "\nPassword lock: ", pw_on,
        "\nPassword timeout: ", pwtimeout,
        "\n===========",
        sep="")



if(__name__ == "__main__"):

  try:
    init()
    main()
  except KeyboardInterrupt:
    print("\r\rServer stopped.")