# 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 "TCP_generic.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

#local files
import Stateful_module
import encryption
import client
import common

class TCP_generic_thread(Stateful_module.Stateful_thread):
	def __init__(self, threadID, serverorclient, tunnel, packetselector, comms_socket, client_addr, authentication, encryption_module, verbosity, config, module_name):
		super(TCP_generic_thread, self).__init__()
		threading.Thread.__init__(self)
		self._stop = False
		self.threadID = threadID
		self.tunnel_r = None
		self.tunnel_w = tunnel
		self.packetselector = packetselector
		self.comms_socket = comms_socket
		self.client_addr = client_addr
		self.authentication = authentication
		self.verbosity = verbosity
		self.serverorclient = serverorclient
		self.config = config
		self.module_name = module_name
		self.check_result = None
		self.timeout = 3.0
		self.partial_message = ""
		self.module_short = "TCP"

		self.client = None
		self.authenticated = False

		self.encryption = encryption.Encryption_details()
		self.encryption.set_module(encryption_module)

		return

	def communication_initialization(self):
		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)

		common.internal_print("{0} sent: {1}".format(self.module_short, len(transformed_message)), 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(struct.pack(">H", len(transformed_message))+transformed_message)
			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:
			length = struct.unpack(">H", message[0:2])[0]+2
			if len(message) >= length:
				messages.append(self.transform(self.encryption, message[2:length], 0))
				common.internal_print("{0} read: {1}".format(self.module_short, 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

	def cleanup(self):
		try:
			self.comms_socket.close()
		except:
			pass

		if self.serverorclient:
			self.packetselector.delete_client(self.client)

	def communication_win(self, is_check):
		import win32event
		import win32file
		import win32api
		import pywintypes
		import winerror

		# event for the socket
		hEvent_sock = win32event.CreateEvent(None, 0, 0, None)
		win32file.WSAEventSelect(self.comms_socket, hEvent_sock, win32file.FD_READ)

		# event, overlapped struct for the pipe or tunnel
		hEvent_pipe = win32event.CreateEvent(None, 0, 0, None) # for reading from the pipe
		overlapped_pipe = pywintypes.OVERLAPPED()
		overlapped_pipe.hEvent = hEvent_pipe

		# buffer for the packets
		message_readfile = win32file.AllocateReadBuffer(4096)

		# showing if we already async reading or not
		not_reading_already = True
		first_run = True
		while not self._stop:
			try:
				if not self.tunnel_r:
					# user is not authenticated yet, so there is no pipe
					# only checking the socket for data
					rc = win32event.WaitForSingleObject(hEvent_sock, int(self.timeout*1000))
					if rc == winerror.WAIT_TIMEOUT:
						# timed out, just rerun and wait
						continue
				else:
					# client mode so we have the socket and tunnel as well
					# or the client authenticated and the pipe was created
					if first_run or not_reading_already:
						# no ReadFile was called before or finished, so we
						# are calling it again
						hr, _ = win32file.ReadFile(self.tunnel_r, message_readfile, overlapped_pipe)
						not_reading_already = first_run = False

					if (hr == winerror.ERROR_IO_PENDING):
						# well, this was an async read, so we need to wait
						# until it happens
						rc = win32event.WaitForMultipleObjects([hEvent_sock, hEvent_pipe], 0, int(self.timeout*1000))
						if rc == winerror.WAIT_TIMEOUT:
							# timed out, just rerun and wait
							continue
					else:
						if hr != 0:
							common.internal_print("{0} ReadFile failed: {1}".format(self.module_short, hr), -1)
							raise

				if rc < 0x80: # STATUS_ABANDONED_WAIT_0
					if rc == 0:
						# socket got signalled
						not_reading_already = False
						messages = self.recv()
						for message in messages:
							# looping through the messages from socket
							if len(message) == 0:
								# this could happen when the socket died or
								# partial message was read.
								continue

							if common.is_control_channel(message[0:1]):
								# parse control messages
								if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], None):
									continue
								else:
									self.stop()
									break

							if self.authenticated:
								try:
									# write packet to the tunnel
									self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):])
								except OSError as e:
									print(e) # wut?
								except Exception as e:
									if e.args[0] == 995:
										common.internal_print("Interface disappered, exiting thread: {0}".format(e), -1)
										self.stop()
										continue

					if rc == 1:
						# pipe/tunnel got signalled
						not_reading_already = True
						if (overlapped_pipe.InternalHigh < 4) or (message_readfile[0:1] != "\x45"): #Only care about IPv4
							# too small which should not happen or not IPv4, so we just drop it.
							continue

						# reading out the packet from the buffer and discarding the rest
						readytogo = message_readfile[0:overlapped_pipe.InternalHigh]
						self.send(common.DATA_CHANNEL_BYTE, readytogo, None)

			except win32api.error as e:
				common.internal_print("TCP Exception: {0}".format(e), -1)

		self.cleanup()

		return True


	def communication_unix(self, is_check):
		rlist = [self.comms_socket]
		wlist = []
		xlist = []

		while not self._stop:
			if self.tunnel_r:
				rlist = [self.tunnel_r, self.comms_socket]
			try:
				readable, writable, exceptional = select.select(rlist, wlist, xlist, self.timeout)
			except select.error, e:
				break	
			if self._stop:
				self.comms_socket.close()
				break
			try:
				for s in readable:
					if (s is self.tunnel_r) and not self._stop:
						message = self.packet_reader(self.tunnel_r, True, self.serverorclient)
						while True:
							if (len(message) < 4) or (message[0:1] != "\x45"): #Only care about IPv4
								break
							packetlen = struct.unpack(">H", message[2:4])[0] # IP Total length
							if packetlen > len(message):
								message += self.packet_reader(self.tunnel_r, False, self.serverorclient)

							readytogo = message[0:packetlen]
							message = message[packetlen:]
							self.send(common.DATA_CHANNEL_BYTE, readytogo, None)

					if (s is self.comms_socket) and not self._stop:
						messages = self.recv()
						for message in messages:
							if len(message) == 0:
								continue

							if common.is_control_channel(message[0:1]):
								if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], None):
									continue
								else:
									self.stop()
									break

							if self.authenticated:
								try:
									self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):])
								except OSError as e:
									print(e) # wut?
								except Exception as e:
									print(e)

			except (socket.error, OSError, IOError):
				if self.serverorclient:
					common.internal_print("Client lost. Closing down thread.", -1)
					self.cleanup()

					return
				if not self.serverorclient:
					common.internal_print("Server lost. Closing connection.", -1)
					self.comms_socket.close()
				break
			except:
				print("another error")
				raise

		self.cleanup()

		return True


class TCP_generic(Stateful_module.Stateful_module):

	module_name = "TCP generic"
	module_configname = "TCP_generic"
	module_description = """Generic TCP module that can listen on any port.
		This module lacks of any encryption or encoding, which comes to the interface
		goes to the socket back and forth. Nothing special.
		"""
	module_os_support = common.OS_LINUX | common.OS_MACOSX | common.OS_WINDOWS | common.OS_FREEBSD

	def __init__(self):
		super(TCP_generic, self).__init__()
		self.server_socket = None

		return

	def stop(self):
		self._stop = True

		if self.threads:
			for t in self.threads:
				t.stop()
		
		# not so nice solution to get rid of the block of listen()
		# unfortunately close() does not help on the block
		try:
			server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			serverbind = self.config.get("Global", "serverbind")
			if serverbind == "0.0.0.0":
				# windows does not like to connect to 0.0.0.0
				serverbind = "127.0.0.1"

			server_socket.connect((serverbind, int(self.config.get(self.get_module_configname(), "serverport"))))
		except:
			pass

		return

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

			return False

		try:
			convert = int(self.config.get(self.get_module_configname(), "serverport"))
		except:
			common.internal_print("'serverport' is not an integer in '{0}' section".format(self.get_module_configname()), -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 = TCP_generic_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)
			if self._stop:
				self.stop()

		except socket.error as exception:
			# [Errno 98] Address already in use
			if ((self.os_type == common.OS_LINUX) and (exception.args[0] == 98)) or ((self.os_type == common.OS_MACOSX) and (exception.args[0] == 48)):
				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)
			else:
				raise

		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("Global", "remoteserverip"), int(self.config.get(self.get_module_configname(), "serverport"))))

			client_fake_thread = TCP_generic_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 as e:
			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("Global", "remoteserverip"), int(self.config.get(self.get_module_configname(), "serverport"))))
			client_fake_thread = TCP_generic_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 cleanup(self, socket):
		common.internal_print("Shutting down module: {0}".format(self.get_module_name()))
		socket.close()

		return