diff --git a/FreeTAKServer/core/configuration/MainConfig.py b/FreeTAKServer/core/configuration/MainConfig.py index 21eac076..2a107f22 100644 --- a/FreeTAKServer/core/configuration/MainConfig.py +++ b/FreeTAKServer/core/configuration/MainConfig.py @@ -8,7 +8,7 @@ # the version information of the server (recommended to leave as default) -FTS_VERSION = "FreeTAKServer-2.0.11 Alpha" +FTS_VERSION = "FreeTAKServer-2.0.13 Alpha" API_VERSION = "1.9.6" # TODO Need to find a better way to determine python version at runtime PYTHON_VERSION = "python3.8" diff --git a/FreeTAKServer/services/ssl_cot_service/controllers/ReceiveConnections.py b/FreeTAKServer/services/ssl_cot_service/controllers/ReceiveConnections.py index 97849ddc..169ac8b6 100644 --- a/FreeTAKServer/services/ssl_cot_service/controllers/ReceiveConnections.py +++ b/FreeTAKServer/services/ssl_cot_service/controllers/ReceiveConnections.py @@ -20,10 +20,9 @@ from FreeTAKServer.core.configuration.ClientReceptionLoggingConstants import ClientReceptionLoggingConstants from FreeTAKServer.core.configuration.LoggingConstants import LoggingConstants -from FreeTAKServer.model.RawConnectionInformation import RawConnectionInformation as sat +from FreeTAKServer.services.ssl_cot_service.model.raw_ssl_connection_information import RawSSLConnectionInformation as sat from FreeTAKServer.core.configuration.CreateLoggerController import CreateLoggerController from FreeTAKServer.core.configuration.ReceiveConnectionsConstants import ReceiveConnectionsConstants -from FreeTAKServer.model.RawConnectionInformation import RawConnectionInformation from FreeTAKServer.services.ssl_cot_service.controllers.SSLSocketController import SSLSocketController loggingConstants = LoggingConstants(log_name="FTS_ReceiveConnections") @@ -80,62 +79,68 @@ def listen(self, sock): # logger.debug('receive connection started') try: client, address = sock.accept() - #client = SSLSocketController().wrap_client_sock(client) + ssl_client = SSLSocketController().wrap_client_socket(client) except ssl.SSLError as ex: print(ex) - client.close() + self.disconnect_socket(client, ssl_client) logger.warning('ssl error thrown in connection attempt ' + str(ex)) return -1 except asyncio.TimeoutError as ex: - client.close() + self.disconnect_socket(client, ssl_client) logger.warning('timeout error thrown in connection attempt '+str(ex)) return -1 logger.info('client connected over ssl ' + str(address) + ' ' + str(time.time())) # wait to receive client try: - events = self.receive_connection_data(client=client) + events = self.receive_connection_data(client=ssl_client) except Exception: try: - events = self.receive_connection_data(client=client) + events = self.receive_connection_data(client=ssl_client) except Exception as exb: - client.close() + self.disconnect_socket(client, ssl_client) logger.warning("receiving connection data from client failed with exception "+str(exb)) return -1 # TODO: move out to separate function if events.text == TEST_SUCCESS: - client.send(b'success') - client.settimeout(0) # set the socket to non blocking + ssl_client.send(b'success') + ssl_client.settimeout(0) # set the socket to non blocking logger.info(loggingConstants.RECEIVECONNECTIONSLISTENINFO) # establish the socket array containing important information about the client - raw_connection_information = self.instantiate_client_object(address, client, events) + raw_connection_information = self.instantiate_client_object(address, client, ssl_client, events) logger.info("client accepted") try: if socket is not None and raw_connection_information.xmlString != b'': return raw_connection_information else: logger.warning("final socket entry is invalid") - client.close() + self.disconnect_socket(client, ssl_client) return -1 except Exception as ex: - client.close() + self.disconnect_socket(client, ssl_client) logger.warning('exception in returning data ' + str(ex)) return -1 except Exception as ex: logger.warning(loggingConstants.RECEIVECONNECTIONSLISTENERROR) try: - client.close() + self.disconnect_socket(client, ssl_client) except Exception as ex: pass finally: return -1 - def instantiate_client_object(self, address, client, events): + def disconnect_socket(self, client, ssl_client): + ssl_client.shutdown(socket.SHUT_RDWR) + ssl_client.close() + client.close() + + def instantiate_client_object(self, address, unwrapped_client, client, events): raw_connection_information = sat() raw_connection_information.ip = address[0] raw_connection_information.socket = client + raw_connection_information.unwrapped_sock = unwrapped_client raw_connection_information.xmlString = etree.tostring(events.findall('event')[0]).decode('utf-8') return raw_connection_information diff --git a/FreeTAKServer/services/ssl_cot_service/controllers/SSLSocketController.py b/FreeTAKServer/services/ssl_cot_service/controllers/SSLSocketController.py index 08939183..dc3df8e8 100644 --- a/FreeTAKServer/services/ssl_cot_service/controllers/SSLSocketController.py +++ b/FreeTAKServer/services/ssl_cot_service/controllers/SSLSocketController.py @@ -26,9 +26,21 @@ def createSocket(self): self.MainSocket.sock.setsockopt(self.MainSocket.solSock, self.MainSocket.soReuseAddr, self.MainSocket.sockProto) self.MainSocket.sock.bind((self.MainSocket.ip, self.MainSocket.port)) - self.MainSocket.sock = context.wrap_socket(self.MainSocket.sock, server_side=True) + #self.MainSocket.sock = context.wrap_socket(self.MainSocket.sock, server_side=True) return self.MainSocket.sock + def wrap_client_socket(self, socket): + context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER) + context.load_verify_locations(cafile=self.MainSocket.CA) + context.options |= ssl.OP_NO_SSLv3 + context.options |= ssl.OP_NO_SSLv2 + context.verify_mode = ssl.CERT_REQUIRED + context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF + context.load_cert_chain(certfile=self.MainSocket.pemDir, keyfile=self.MainSocket.keyDir, + password=self.MainSocket.password, ) + sock = context.wrap_socket(socket, server_side=True) + return sock + def createClientSocket(self, serverIP): context = ssl.SSLContext() context.load_verify_locations(cafile=self.MainSocket.CA) @@ -38,5 +50,5 @@ def createClientSocket(self, serverIP): context.check_hostname = False context.set_ciphers('DEFAULT@SECLEVEL=1') self.MainSocket.sock = socket.socket(self.MainSocket.socketAF, self.MainSocket.socketSTREAM) - self.MainSocket.sock = context.wrap_socket(self.MainSocket.sock) + # self.MainSocket.sock = context.wrap_socket(self.MainSocket.sock) return self.MainSocket.sock \ No newline at end of file diff --git a/FreeTAKServer/services/ssl_cot_service/model/raw_ssl_connection_information.py b/FreeTAKServer/services/ssl_cot_service/model/raw_ssl_connection_information.py new file mode 100644 index 00000000..d383c13f --- /dev/null +++ b/FreeTAKServer/services/ssl_cot_service/model/raw_ssl_connection_information.py @@ -0,0 +1,6 @@ +from FreeTAKServer.model.RawConnectionInformation import RawConnectionInformation + +class RawSSLConnectionInformation(RawConnectionInformation): + def __init__(self): + super().__init__() + self.unwrapped_sock = None \ No newline at end of file diff --git a/FreeTAKServer/services/ssl_cot_service/model/ssl_client_information.py b/FreeTAKServer/services/ssl_cot_service/model/ssl_client_information.py new file mode 100644 index 00000000..7a641973 --- /dev/null +++ b/FreeTAKServer/services/ssl_cot_service/model/ssl_client_information.py @@ -0,0 +1,39 @@ +# pylint: disable=trailing-whitespace +####################################################### +# +# ClientInformation.py +# Python implementation of the Class ClientInformation +# Generated by Enterprise Architect +# Created on: 21-May-2020 9:47:19 AM +# Original author: Natha Paquette +# +####################################################### +import copy + + +class SSLClientInformation: + def __init__(self): + self.modelObject = '' + self.alive = "" + self.dataQueue = "" + self.ID = "" + self.idData = "" + self.IP = "" + self.receptionHandler = "" + self.sendData = "" + self.socket = "" + self.unwrapped_socket = "" + self.type = "clientInformation" + + def __deepcopy__(self, memodict=dict): + returned = ClientInformation() + returned.modelObject = copy.deepcopy(self.modelObject) + returned.alive = copy.deepcopy(self.alive) + returned.dataQueue = copy.deepcopy(self.dataQueue) + returned.ID = copy.deepcopy(self.ID) + returned.idData = copy.deepcopy(self.idData) + returned.IP = copy.deepcopy(self.IP) + returned.receptionHandler = copy.deepcopy(self.receptionHandler) + returned.sendData = copy.deepcopy(self.sendData) + returned.type = copy.deepcopy(self.type) + return returned diff --git a/FreeTAKServer/services/ssl_cot_service/ssl_cot_service_main.py b/FreeTAKServer/services/ssl_cot_service/ssl_cot_service_main.py index 02e4304c..2f4b4e9f 100644 --- a/FreeTAKServer/services/ssl_cot_service/ssl_cot_service_main.py +++ b/FreeTAKServer/services/ssl_cot_service/ssl_cot_service_main.py @@ -10,6 +10,7 @@ from opentelemetry.trace import Status, StatusCode from typing import List, Union, Dict from FreeTAKServer.services.ssl_cot_service.controllers.send_component_data_controller import SendComponentDataController +from FreeTAKServer.services.ssl_cot_service.model.raw_ssl_connection_information import RawSSLConnectionInformation from FreeTAKServer.services.ssl_cot_service.model.ssl_cot_connection import SSLCoTConnection from digitalpy.core.service_management.digitalpy_service import DigitalPyService @@ -240,7 +241,7 @@ def get_client_information(self): elif ( client_id in user_dict.keys() - and len(self.client_information_queue[client_id]) == 2 + and len(self.client_information_queue[client_id]) == 3 ): self.client_information_queue[client_id][1] = user_dict[client_id] @@ -319,7 +320,7 @@ def send_user_connection_geo_chat(self, clientInformation): else: return 1 - def clientConnected(self, raw_connection_information: RawCoT): + def clientConnected(self, raw_connection_information: RawSSLConnectionInformation): """Controls the client connection sequence, calling methods which perform the following: 1. Instantiate the client object 2. Share the client with core @@ -377,7 +378,7 @@ def clientConnected(self, raw_connection_information: RawCoT): self.add_service_user(clientInformation=clientInformation) # Add client info to queue - self.client_information_queue[clientInformation.modelObject.uid] = [clientInformation.socket, clientInformation] + self.client_information_queue[clientInformation.modelObject.uid] = [clientInformation.socket, clientInformation, raw_connection_information.unwrapped_sock] # instantiate an object_id with a value of the client uid object_id = ObjectFactory.get_new_instance("ObjectId", dynamic_configuration={"id": clientInformation.modelObject.uid, "type": "connection"}) @@ -475,6 +476,7 @@ def clientDisconnected(self, clientInformation: User): clientInformation.clientInformation ][1] sock = self.client_information_queue[clientInformation.user_id][0] + unwrapped_sock = self.client_information_queue[clientInformation.user_id][2] except Exception as e: self.logger.critical( "getting sock from client information queue failed " + str(e) @@ -522,7 +524,7 @@ def clientDisconnected(self, clientInformation: User): try: self.remove_service_user(clientInformation=clientInformation) - self.disconnect_socket(sock) + self.disconnect_socket(sock, unwrapped_sock) self.logger.info(loggingConstants.CLIENTDISCONNECTSTART) @@ -568,7 +570,7 @@ def send_disconnect_cot(self, clientInformation): self.messages_to_core_count += 1 self.send_message(disconnect.getObject().clientInformation, disconnect.getObject()) - def disconnect_socket(self, sock: socket.socket) -> None: + def disconnect_socket(self, sock: socket.socket, unwrapped_socket: socket.socket) -> None: """this method is responsible for disconnecting all socket objects :param sock: socket object to be disconnected @@ -590,6 +592,14 @@ def disconnect_socket(self, sock: socket.socket) -> None: + str(e) + "\n".join(traceback.format_stack()) ) + try: + unwrapped_socket.close() + except Exception as e: + self.logger.error( + "error closing unwrapped socket in client disconnection " + + str(e) + + "\n".join(traceback.format_stack()) + ) def component_handler(self, cot): """this method is responsible for handling cases where the cot sent should @@ -1022,11 +1032,11 @@ def handle_regular_data(self, clientDataOutput: List[RawCoT]): # Process the raw CoT data and serialize it CoTOutput = self.monitor_raw_cot(clientDataOutputSingle) - self.logger.info(f"CoT serialized {CoTOutput.modelObject.uid}") - # Skip this iteration if the CoT data is invalid if CoTOutput == 1: continue + + self.logger.info(f"CoT serialized {CoTOutput.modelObject.uid}") # Check if the CoT data is valid and can be sent if self.checkOutput(CoTOutput): diff --git a/setup.py b/setup.py index c240cda8..7182d411 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ packages=find_packages( include=["FreeTAKServer", "FreeTAKServer.*", "*.json", "*.ini", "*.conf"] ), - version="0.2.0.11 Alpha", + version="0.2.0.13", license="EPL-2.0", description="An open source server for the TAK family of applications.", long_description=long_description, @@ -37,7 +37,7 @@ "Flask-SQLAlchemy==2.4.4", "geographiclib==1.52", "geopy==2.2.0", - "greenlet==2.0.0", + "greenlet==2.0.2", "itsdangerous==2.0.1", "testresources==2.0.1", "Jinja2==2.11.2", @@ -46,7 +46,7 @@ "monotonic==1.6", "pathlib2==2.3.7.post1", "protobuf==3.18.3", - "psutil==5.9.0", + "psutil==5.9.4", "pykml==0.2.0", "python-engineio==3.13.2", "python-socketio==4.6.0",