<commit_before>#!/usr/bin/env python2
# -*- coding: <UTF-8> -*-
"""rspet_server.py: RSPET's Server-side script."""
from __future__ import print_function
from sys import argv
from sys import exit as sysexit
from socket import socket, AF_INET, SOCK_STREAM
from socket import error as sock_error
import ssl
import argparse
from datetime import datetime
from thread import start_new_thread
#from threading import Thread # Will bring back at some point
import json
from Plugins.mount import Plugin
import tab


__author__ = "<NAME>"
__copyright__ = "Copyright 2016, Kolokotronis Panagiotis"
__credits__ = ["<NAME>", "<NAME>"]
__license__ = "MIT"
__version__ = "0.3.0"
__maintainer__ = "<NAME>"


class ReturnCodes(object):
    """Enumeration containing the Return Codes of the Server."""
    OK, InvalidSyntax, SocketError, LocalAccessError, RemoteAccessError = range(5)
    OutOfScope, CommandNotFound, InvalidHostID = range(5, 8)


class API(object):
    """Define RSPET Server's Api."""
    def __init__(self, max_conns, ip, port):
        """Initialize Server object."""
        self.server = Server(max_conns, ip, port)
        try:
            start_new_thread(self.server.loop, ())
        except sock_error:
            raise sock_error

    def call_plugin(self, command, args=[]):
        """Call a plugin command"""
        ret = self.server.execute(command, args)
        return {"transition":ret[0],
                "code":ret[1],
                "string":ret[2]}

    def select(self, args=[]):
        """Manage host selection."""
        ret = self.server.select(args)
        return {"transition":None,
                "code":ret[0],
                "string":ret[1]}

    def help(self):
        """
        Temporary. Should interface Server's help when Circular references are removed.
        """
        help_dct = {}
        for cmd in Plugin.__server_cmds__:
            help_dct[cmd] = {'help':Plugin.__server_cmds__[cmd].__doc__,
                             'syntax':Plugin.__help__[cmd],
                             'states':Plugin.__cmd_states__[cmd]}
        return help_dct

    def refresh(self):
        """Interface Server object's clean function."""
        self.server.clean()

    def get_server(self):
        """Return the instance of Server object for low level API."""
        return self.server

    def get_hosts(self):
        """Return hosts by ID."""
        host = self.server.get_hosts()
        ret = {}
        for h_id in host:
            tmp_host = host[h_id]
            ret[h_id] = {"ip":tmp_host.ip,
                         "port":tmp_host.port,
                         "version":str(tmp_host.version),
                         "type":str(tmp_host.type),
                         "system":str(tmp_host.systemtype),
                         "hostname":str(tmp_host.hostname)}
        return ret

    def quit(self):
        self.server.trash()


class Console(object):
    """Provide command line interface for the server."""
    prompt = "~$ " # Current command prompt.
    states = {} # Dictionary that defines available states.
    state = "basic" #CLI "entry" state.

    def __init__(self, max_conns, ip, port):
        """Start server and initialize states."""
        self.server = Server(max_conns, ip, port)
        #If done directly @ Class attributes, Class funcs are not recognised.
        Console.states['basic'] = Console._basic
        Console.states['connected'] = Console._connected
        Console.states['multiple'] = Console._multiple
        Console.states['all'] = Console._all

    def trash(self):
        """Delete Console."""
        self.server.trash()

    def loop(self):
        """Main CLI loop"""
        self._logo()
        try:
            start_new_thread(self.server.loop, ())
        except sock_error:
            print("Address is already in use")
            sysexit()

        while not self.server.quit_signal:
            try:
                cmd = raw_input(Console.prompt)
            except KeyboardInterrupt:
                raise KeyboardInterrupt

            cmdargs = cmd.split(" ")
            #"Sanitize" user input by stripping spaces.
            cmdargs = [x for x in cmdargs if x != ""]
            if cmdargs == []: #Check if command was empty.
                continue
            cmd = cmdargs[0]
            del cmdargs[0]
            #Execute command.
            try:
                if Console.state in Plugin.__cmd_states__[cmd]:
                    results = self.server.execute(cmd, cmdargs)
                else:
                    results = [None, 5, "Command used out of scope."]
            except KeyError:
                results = [None, 6, "Command not found. Try help for a list of all commands!"]
            tmp_state = results[0]
            #return_code = results[1] # Future use ? Also API.
            return_string = results[2]
            if return_string != "":
                print(return_string)
            try:
                Console.states[tmp_state](self) #State transition.
            except KeyError: #If none is returned make no state change.
                continue
        self.server.trash()

    def _basic(self):
        self.server.clean()
        self.server.select([])
        Console.prompt = "~$ "
        Console.state = "basic"

    def _connected(self):
        try:
            Console.prompt = "[%s]~$ " % self.server.selected[0].ip
        except IndexError:
            pass
        else:
            Console.state = "connected"

    def _multiple(self):
        if len(self.server.selected) != 0:
            Console.prompt = "[MULTIPLE]~$ "
            Console.state = "multiple"

    def _all(self):
        if len(self.server.selected) != 0:
            Console.prompt = "[ALL]~$ "
            Console.state = "multiple"

    def _logo(self):
        """Print logo and Authorship/License."""
        print(r"#####################################################")
        print(r"__________  _________________________________________")
        print(r"\______   \/   _____/\______   \_   _____/\__    ___/")
        print(r" |       _/\_____  \  |     ___/|    __)_   |    |   ")
        print(r" |    |   \/        \ |    |    |        \  |    |   ")
        print(r" |____|_  /_______  / |____|   /_______  /  |____|   ")
        print(r"        \/        \/                   \/            ")
        print(r"")
        print(r" -Author: panagiks (http://panagiks.xyz)")
        print(r" -Co-Author: dzervas (http://dzervas.gr)")
        print(r" -Licence: MIT @ panagiks")
        print(r"#####################################################")
        print(r"")


class Server(object):
    """Main class of the server. Manage server socket, selections and call plugins."""
    #hosts = [] # List of hosts
    #selected = [] # List of selected hosts
    #plugins = [] # List of active plugins
    #config = {} # Reserved for future use (dynamic plug-in loading).

    def __init__(self, max_conns=5, ip="0.0.0.0", port="9000"):
        """Start listening on socket."""
        self.ip = ip
        self.port = port
        self.max_conns = max_conns
        self.sock = socket(AF_INET, SOCK_STREAM)
        self.serial = 0
        self.quit_signal = False
        self.hosts = {} # List of hosts
        self.selected = [] # List of selected hosts
        self.plugins = [] # List of active plugins
        self.log_opt = [] # List of Letters. Indicates logging level

        with open("config.json") as json_config:
            self.config = json.load(json_config)
        self.log_opt = self.config["log"]
        self._log("L", "Session Start.")
        for plugin in self.config["plugins"]:
            try:
                __import__("Plugins.%s" % plugin)
                self._log("L", "%s plugin loaded." % plugin)
            except ImportError:
                self._log("E", "%s plugin failed to load." % plugin)
        try:
            self.sock.bind((ip, int(port)))
            self.sock.listen(max_conns)
            self._log("L", "Socket bound @ %s:%s." %(self.ip, self.port))
        except sock_error:
            print("Something went wrong during binding & listening")
            self._log("E", "Error binding socket @ %s:%s." %(self.ip, self.port))
            sysexit()

    def trash(self):
        """Safely closes all sockets"""
        for host in self.hosts:
            self.hosts[host].trash()
        self.clean()
        self.select([])
        self.sock.close()

    def _log(self, level, action):
        if level not in self.log_opt:
            return
        timestamp = datetime.now()
        with open("log.txt", 'a') as logfile:
            logfile.write("%s : [%s/%s/%s %s:%s:%s] => %s\n" %(level,
                                                               str(timestamp.day),
                                                               str(timestamp.month),
                                                               str(timestamp.year),
                                                               str(timestamp.hour),
                                                               str(timestamp.minute),
                                                               str(timestamp.second),
                                                               action))

    def loop(self):
        """Main server loop for accepting connections. Better call it on its own thread"""
        while True:
            try:
                (csock, (ip, port)) = self.sock.accept()
                self._log("L", "New connection from %s:%s" % (str(ip),
                                                              str(port)))
            except sock_error:
                raise sock_error
            csock = ssl.wrap_socket(csock, server_side=True, certfile="server.crt",
                                    keyfile="server.key")
            self.hosts[str(self.serial)] = Host(csock, ip, port, self.serial)
            self.serial += 1

    def select(self, ids=None):
        """Selects given host(s) based on ids
<commit_msg>Add TLS error handling and strict version enforcement<commit_after>#!/usr/bin/env python2
# -*- coding: <UTF-8> -*-
"""rspet_server.py: RSPET's Server-side script."""
from __future__ import print_function
from sys import argv
from sys import exit as sysexit
from socket import socket, AF_INET, SOCK_STREAM
from socket import error as sock_error
import ssl
import argparse
from datetime import datetime
from thread import start_new_thread
#from threading import Thread # Will bring back at some point
import json
from Plugins.mount import Plugin
import tab


__author__ = "<NAME>"
__copyright__ = "Copyright 2016, Ko<NAME>"
__credits__ = ["<NAME>", "<NAME>"]
__license__ = "MIT"
__version__ = "0.3.0"
__maintainer__ = "Ko<NAME>"


class ReturnCodes(object):
    """Enumeration containing the Return Codes of the Server."""
    OK, InvalidSyntax, SocketError, LocalAccessError, RemoteAccessError = range(5)
    OutOfScope, CommandNotFound, InvalidHostID = range(5, 8)


class API(object):
    """Define RSPET Server's Api."""
    def __init__(self, max_conns, ip, port):
        """Initialize Server object."""
        self.server = Server(max_conns, ip, port)
        try:
            start_new_thread(self.server.loop, ())
        except sock_error:
            raise sock_error

    def call_plugin(self, command, args=[]):
        """Call a plugin command"""
        ret = self.server.execute(command, args)
        return {"transition":ret[0],
                "code":ret[1],
                "string":ret[2]}

    def select(self, args=[]):
        """Manage host selection."""
        ret = self.server.select(args)
        return {"transition":None,
                "code":ret[0],
                "string":ret[1]}

    def help(self):
        """
        Temporary. Should interface Server's help when Circular references are removed.
        """
        help_dct = {}
        for cmd in Plugin.__server_cmds__:
            help_dct[cmd] = {'help':Plugin.__server_cmds__[cmd].__doc__,
                             'syntax':Plugin.__help__[cmd],
                             'states':Plugin.__cmd_states__[cmd]}
        return help_dct

    def refresh(self):
        """Interface Server object's clean function."""
        self.server.clean()

    def get_server(self):
        """Return the instance of Server object for low level API."""
        return self.server

    def get_hosts(self):
        """Return hosts by ID."""
        host = self.server.get_hosts()
        ret = {}
        for h_id in host:
            tmp_host = host[h_id]
            ret[h_id] = {"ip":tmp_host.ip,
                         "port":tmp_host.port,
                         "version":str(tmp_host.version),
                         "type":str(tmp_host.type),
                         "system":str(tmp_host.systemtype),
                         "hostname":str(tmp_host.hostname)}
        return ret

    def quit(self):
        self.server.trash()


class Console(object):
    """Provide command line interface for the server."""
    prompt = "~$ " # Current command prompt.
    states = {} # Dictionary that defines available states.
    state = "basic" #CLI "entry" state.

    def __init__(self, max_conns, ip, port):
        """Start server and initialize states."""
        self.server = Server(max_conns, ip, port)
        #If done directly @ Class attributes, Class funcs are not recognised.
        Console.states['basic'] = Console._basic
        Console.states['connected'] = Console._connected
        Console.states['multiple'] = Console._multiple
        Console.states['all'] = Console._all

    def trash(self):
        """Delete Console."""
        self.server.trash()

    def loop(self):
        """Main CLI loop"""
        self._logo()
        try:
            start_new_thread(self.server.loop, ())
        except sock_error:
            print("Address is already in use")
            sysexit()

        while not self.server.quit_signal:
            try:
                cmd = raw_input(Console.prompt)
            except KeyboardInterrupt:
                raise KeyboardInterrupt

            cmdargs = cmd.split(" ")
            #"Sanitize" user input by stripping spaces.
            cmdargs = [x for x in cmdargs if x != ""]
            if cmdargs == []: #Check if command was empty.
                continue
            cmd = cmdargs[0]
            del cmdargs[0]
            #Execute command.
            try:
                if Console.state in Plugin.__cmd_states__[cmd]:
                    results = self.server.execute(cmd, cmdargs)
                else:
                    results = [None, 5, "Command used out of scope."]
            except KeyError:
                results = [None, 6, "Command not found. Try help for a list of all commands!"]
            tmp_state = results[0]
            #return_code = results[1] # Future use ? Also API.
            return_string = results[2]
            if return_string != "":
                print(return_string)
            try:
                Console.states[tmp_state](self) #State transition.
            except KeyError: #If none is returned make no state change.
                continue
        self.server.trash()

    def _basic(self):
        self.server.clean()
        self.server.select([])
        Console.prompt = "~$ "
        Console.state = "basic"

    def _connected(self):
        try:
            Console.prompt = "[%s]~$ " % self.server.selected[0].ip
        except IndexError:
            pass
        else:
            Console.state = "connected"

    def _multiple(self):
        if len(self.server.selected) != 0:
            Console.prompt = "[MULTIPLE]~$ "
            Console.state = "multiple"

    def _all(self):
        if len(self.server.selected) != 0:
            Console.prompt = "[ALL]~$ "
            Console.state = "multiple"

    def _logo(self):
        """Print logo and Authorship/License."""
        print(r"#####################################################")
        print(r"__________  _________________________________________")
        print(r"\______   \/   _____/\______   \_   _____/\__    ___/")
        print(r" |       _/\_____  \  |     ___/|    __)_   |    |   ")
        print(r" |    |   \/        \ |    |    |        \  |    |   ")
        print(r" |____|_  /_______  / |____|   /_______  /  |____|   ")
        print(r"        \/        \/                   \/            ")
        print(r"")
        print(r" -Author: panagiks (http://panagiks.xyz)")
        print(r" -Co-Author: dzervas (http://dzervas.gr)")
        print(r" -Licence: MIT @ panagiks")
        print(r"#####################################################")
        print(r"")


class Server(object):
    """Main class of the server. Manage server socket, selections and call plugins."""
    #hosts = [] # List of hosts
    #selected = [] # List of selected hosts
    #plugins = [] # List of active plugins
    #config = {} # Reserved for future use (dynamic plug-in loading).

    def __init__(self, max_conns=5, ip="0.0.0.0", port="9000"):
        """Start listening on socket."""
        self.ip = ip
        self.port = port
        self.max_conns = max_conns
        self.sock = socket(AF_INET, SOCK_STREAM)
        self.serial = 0
        self.quit_signal = False
        self.hosts = {} # List of hosts
        self.selected = [] # List of selected hosts
        self.plugins = [] # List of active plugins
        self.log_opt = [] # List of Letters. Indicates logging level

        with open("config.json") as json_config:
            self.config = json.load(json_config)
        self.log_opt = self.config["log"]
        self._log("L", "Session Start.")
        for plugin in self.config["plugins"]:
            try:
                __import__("Plugins.%s" % plugin)
                self._log("L", "%s plugin loaded." % plugin)
            except ImportError:
                self._log("E", "%s plugin failed to load." % plugin)
        try:
            self.sock.bind((ip, int(port)))
            self.sock.listen(max_conns)
            self._log("L", "Socket bound @ %s:%s." %(self.ip, self.port))
        except sock_error:
            print("Something went wrong during binding & listening")
            self._log("E", "Error binding socket @ %s:%s." %(self.ip, self.port))
            sysexit()

    def trash(self):
        """Safely closes all sockets"""
        for host in self.hosts:
            self.hosts[host].trash()
        self.clean()
        self.select([])
        self.sock.close()

    def _log(self, level, action):
        if level not in self.log_opt:
            return
        timestamp = datetime.now()
        with open("log.txt", 'a') as logfile:
            logfile.write("%s : [%s/%s/%s %s:%s:%s] => %s\n" %(level,
                                                               str(timestamp.day),
                                                               str(timestamp.month),
                                                               str(timestamp.year),
                                                               str(timestamp.hour),
                                                               str(timestamp.minute),
                                                               str(timestamp.second),
                                                               action))

    def loop(self):
        """Main server loop for accepting connections. Better call it on its own thread"""
        while True:
            try:
                (csock, (ip, port)) = self.sock.accept()
                self._log("L", "New connection from %s:%s" % (str(ip),
                                                              str(port)))
            except sock_error:
                raise sock_error
            try:
                csock = ssl.wrap_socket(csock, server_side=True, certfile="server.crt",
                                        keyfile="server.key",
                                        ssl_version=ssl.PROTOCOL_TLSv1_2)
            except AttributeError: # All PROTOCOL consts are merged on TLS in Python2.7.13
                csock = ssl.wrap_socket(csock, server_side=True, certfile="server.crt",
                                        keyfile="server.key",
                                        ssl_version=ssl.PROTOCOL_TLS)
            self.hosts[str(self.serial)] = Host(csock, ip, port, self.serial)
            self.serial += 1

    def select(self, ids=None):
        """Selects given host(s) based on ids

{'fraction_non_alphanumeric': 0.12832741252615698, 'fraction_numerical': 0.003990461822959755, 'mean_word_length': 1.6615723351897422, 'pattern_counts': {'":': 26, '<': 14, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 45, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3704625', 'n_tokens_mistral': 5646, 'n_tokens_neox': 5433, 'n_words': 1429}