# MIT License

# Copyright (c) 2017 Balazs Bucsay

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys

if "WebSocket.py" in sys.argv[0]:
	print("[-] Instead of poking around just try: python xfltreat.py --help")
	sys.exit(-1)

import socket
import time
import select
import os
import struct
import threading
import base64

#local files
import Stateful_module
import TCP_generic
import client
import common
import support.websocket_proto as WebSocket_proto

class WebSocket_thread(TCP_generic.TCP_generic_thread):
	def __init__(self, threadID, serverorclient, tunnel, packetselector, comms_socket, client_addr, authentication, encryption_module, verbosity, config, module_name):
		super(WebSocket_thread, self).__init__(threadID, serverorclient, tunnel, packetselector, comms_socket, client_addr, authentication, encryption_module, verbosity, config, module_name)

		self.WebSocket_proto = WebSocket_proto.WebSocket_Proto()

		return

	def communication_initialization(self):
		try:
		
			common.internal_print("Waiting for upgrade request", 0, self.verbosity, common.DEBUG)
			response = self.comms_socket.recv(4096)

			if len(response) == 0:
				common.internal_print("Connection was dropped", 0, self.verbosity, common.DEBUG)
				self.cleanup()
				sys.exit(-1)
			handshake_key = self.WebSocket_proto.get_handshake_init(response)
			if handshake_key == None:
				common.internal_print("No WebSocket-Key in request", -1, self.verbosity, common.DEBUG)
				self.cleanup()
				sys.exit(-1)

			handshake = self.WebSocket_proto.calculate_handshake(handshake_key)
			response = self.WebSocket_proto.switching_protocol(handshake)
			self.comms_socket.send(response)
		except:
			common.internal_print("Socket error", -1, self.verbosity, common.DEBUG)
			self.cleanup()
			sys.exit(-1)

		return

	def send(self, channel_type, message, additional_data):
		if channel_type == common.CONTROL_CHANNEL_BYTE:
			transformed_message = self.transform(self.encryption, common.CONTROL_CHANNEL_BYTE+message, 1)
		else:
			transformed_message = self.transform(self.encryption, common.DATA_CHANNEL_BYTE+message, 1)

		websocket_msg = self.WebSocket_proto.build_message(self.serverorclient, 2, transformed_message)

		common.internal_print("WebSocket sent: {0} -> {1}".format(len(transformed_message), len(websocket_msg)), 0, self.verbosity, common.DEBUG)

		# WORKAROUND?!
		# Windows: It looks like when the buffer fills up the OS does not do
		# congestion control, instead throws and exception/returns with
		# WSAEWOULDBLOCK which means that we need to try it again later.
		# So we sleep 100ms and hope that the buffer has more space for us.
		# If it does then it sends the data, otherwise tries it in an infinite
		# loop...
		while True:
			try:
				return self.comms_socket.send(websocket_msg)
			except socket.error as se:
				if se.args[0] == 10035: # WSAEWOULDBLOCK
					time.sleep(0.1)
					pass
				else:
					raise

	def recv(self):
		messages = []
		message = self.partial_message + self.comms_socket.recv(4096)
		if len(message) == len(self.partial_message):
			self._stop = True

		if len(message) < 2:
			return messages

		while True:
			length2b = message[0:2]
			length_type = self.WebSocket_proto.get_length_type(length2b)
			if length_type == -1:
				common.internal_print("Malformed WebSocket packet", -1, self.verbosity, common.DEBUG)
				return ""

			masked = self.WebSocket_proto.is_masked(length2b)
			header_length = self.WebSocket_proto.get_header_length(masked, length_type)
			
			if message < header_length:
				common.internal_print("Malformed WebSocket packet: wrong header length", -1, self.verbosity, common.DEBUG)
				return ""

			data_length = self.WebSocket_proto.get_data_length(message[:header_length], masked, length_type)
			length = data_length + header_length

			if len(message) >= length:
				messages.append(self.transform(self.encryption, self.WebSocket_proto.get_data(message[0:length], header_length, data_length), 0))
				common.internal_print("WebSocket read: {0} -> {1}".format(length, len(messages[len(messages)-1])), 0, self.verbosity, common.DEBUG)
				self.partial_message = ""
				message = message[length:]
			else:
				self.partial_message = message
				break

			if len(message) < 2:
				self.partial_message = message
				break

		return messages


class WebSocket(TCP_generic.TCP_generic):

	module_name = "WebSocket"
	module_configname = "WebSocket"
	module_description = """
		"""
	module_os_support = common.OS_LINUX

	def __init__(self):
		super(WebSocket, self).__init__()
		self.server_socket = None
		self.WebSocket_proto = WebSocket_proto.WebSocket_Proto()

		return

	def websocket_upgrade(self, server_socket):

		request = self.WebSocket_proto.upgrade(base64.b64encode(os.urandom(9)).replace("/", "").replace("+", ""), self.config.get("Global", "remoteserverhost"), self.config.get(self.get_module_configname(), "serverport"), 13)
		server_socket.send(request)
		
		response = server_socket.recv(4096)
		if response[9:12] != "101":
			common.internal_print("Connection failed: {0}".format(response[0:response.find("\n")]), -1)

			return False

		return True

	def sanity_check(self):
		if not super(WebSocket, self).sanity_check():
			return False
		if not self.config.has_option(self.get_module_configname(), "proxyip"):
			common.internal_print("'proxyip' option is missing from '{0}' section".format(self.get_module_configname()), -1)

			return False

		if not self.config.has_option(self.get_module_configname(), "proxyport"):
			common.internal_print("'proxyport' option is missing from '{0}' section".format(self.get_module_configname()), -1)

			return False		

		if not common.is_ipv4(self.config.get(self.get_module_configname(), "proxyip")) and not common.is_ipv6(self.config.get(self.get_module_configname(), "proxyip")):
			common.internal_print("'proxyip' should be ipv4 or ipv6 address in '{0}' section".format(self.get_module_configname()), -1)

			return False


		if not self.config.has_option("Global", "remoteserverhost"):
			common.internal_print("'remoteserverhost' option is missing from 'Global' section", -1)

			return False

		if not common.is_hostname(self.config.get("Global", "remoteserverhost")) and not common.is_ipv4(self.config.get("Global", "remoteserverhost")) and not common.is_ipv6(self.config.get("Global", "remoteserverhost")):
			common.internal_print("'remoteserverhost' should be a hostname 'Global' section", -1)

			return False

		return True

	def serve(self):
		client_socket = server_socket = None
		self.threads = []
		threadsnum = 0

		common.internal_print("Starting module: {0} on {1}:{2}".format(self.get_module_name(), self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport"))))
		
		server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

		try:
			server_socket.bind((self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport"))))
			while not self._stop:
				server_socket.listen(1) #?? 1 ??
				client_socket, client_addr = server_socket.accept()
				common.internal_print(("Client connected: {0}".format(client_addr)), 0, self.verbosity, common.DEBUG)

				threadsnum = threadsnum + 1
				thread = WebSocket_thread(threadsnum, 1, self.tunnel, self.packetselector, client_socket, client_addr, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name())
				thread.start()
				self.threads.append(thread)

		except socket.error as exception:
			# [Errno 98] Address already in use
			if exception.args[0] != 98:
				raise
			else:
				common.internal_print("Starting failed, port is in use: {0} on {1}:{2}".format(self.get_module_name(), self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport"))), -1)

		self.cleanup(server_socket)

		return

	def connect(self):
		try:
			common.internal_print("Starting client: {0}".format(self.get_module_name()))

			client_fake_thread = None

			server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			server_socket.settimeout(3)
			server_socket.connect((self.config.get(self.get_module_configname(), "proxyip"), int(self.config.get(self.get_module_configname(), "proxyport"))))

			if self.websocket_upgrade(server_socket):
				client_fake_thread = WebSocket_thread(0, 0, self.tunnel, None, server_socket, None, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name())
				client_fake_thread.do_hello()
				client_fake_thread.communication(False)

		except KeyboardInterrupt:
			if client_fake_thread:
				client_fake_thread.do_logoff()
			self.cleanup(server_socket)
			raise
		except socket.error:
			common.internal_print("Connection error: {0}".format(self.get_module_name()), -1)
			self.cleanup(server_socket)
			raise

		self.cleanup(server_socket)

		return

	def check(self):
		try:
			common.internal_print("Checking module on server: {0}".format(self.get_module_name()))

			server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			server_socket.settimeout(3)
			server_socket.connect((self.config.get(self.get_module_configname(), "proxyip"), int(self.config.get(self.get_module_configname(), "proxyport"))))
			
			if self.websocket_upgrade(server_socket):
				client_fake_thread = WebSocket_thread(0, 0, None, None, server_socket, None, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name())
				client_fake_thread.do_check()
				client_fake_thread.communication(True)

			self.cleanup(server_socket)

		except socket.timeout:
			common.internal_print("Checking failed: {0}".format(self.get_module_name()), -1)
			self.cleanup(server_socket)
		except socket.error as exception:
			if exception.args[0] == 111:
				common.internal_print("Checking failed: {0}".format(self.get_module_name()), -1)
			else:
				common.internal_print("Connection error: {0}".format(self.get_module_name()), -1)
			self.cleanup(server_socket)

		return

	def get_intermediate_hop(self, config):
		if config.has_option(self.get_module_configname(), "proxyip"):
			if common.is_ipv4(config.get(self.get_module_configname(), "proxyip")) or common.is_ipv6(config.get(self.get_module_configname(), "proxyip")):
				remoteserverip = config.get(self.get_module_configname(), "proxyip")

				return remoteserverip
		return ""