atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -1,34 +1,223 @@
|
|
|
1
|
+
import multiprocessing
|
|
1
2
|
import threading
|
|
2
3
|
import select
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
from typing import Literal, Union, Callable, Any
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import socket
|
|
7
|
+
import shutil
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
import paramiko
|
|
11
|
+
|
|
12
|
+
from ...mitm import initialize_engines
|
|
13
|
+
from ..psutilw import psutil_networks
|
|
14
|
+
from ..certauthw import certauthw
|
|
15
|
+
from ..loggingw import loggingw
|
|
16
|
+
from ... import package_mains_processor
|
|
17
|
+
from ...permissions import permissions
|
|
18
|
+
from ... import filesystem, certificates
|
|
19
|
+
from ...basics import booleans, tracebacks
|
|
7
20
|
from ...print_api import print_api
|
|
21
|
+
from ...ssh_remote import SSHRemote
|
|
22
|
+
|
|
23
|
+
from . import socket_base, creator, process_getter, accepter, statistics_csv, ssl_base, sni
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SocketWrapperPortInUseError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SocketWrapperConfigurationValuesError(Exception):
|
|
31
|
+
pass
|
|
8
32
|
|
|
9
33
|
|
|
10
|
-
|
|
34
|
+
# from ... import queues
|
|
35
|
+
# SNI_QUEUE = queues.NonBlockQueue()
|
|
36
|
+
LOGS_DIRECTORY_NAME: str = 'logs'
|
|
11
37
|
|
|
12
38
|
|
|
13
|
-
# === Socket Wrapper ===================================================================================================
|
|
14
39
|
class SocketWrapper:
|
|
15
40
|
def __init__(
|
|
16
41
|
self,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
ip_address: str,
|
|
43
|
+
port: int,
|
|
44
|
+
engine: initialize_engines.ModuleCategory = None,
|
|
45
|
+
forwarding_dns_service_ipv4_list___only_for_localhost: list = None,
|
|
46
|
+
ca_certificate_name: str = None,
|
|
47
|
+
ca_certificate_filepath: str = None,
|
|
48
|
+
ca_certificate_crt_filepath: str = None,
|
|
49
|
+
install_ca_certificate_to_root_store: bool = False,
|
|
50
|
+
uninstall_unused_ca_certificates_with_ca_certificate_name: bool = False,
|
|
51
|
+
default_server_certificate_usage: bool = False,
|
|
52
|
+
default_server_certificate_name: str = None,
|
|
53
|
+
default_certificate_domain_list: list = None,
|
|
54
|
+
default_server_certificate_directory: str = None,
|
|
55
|
+
sni_custom_callback_function: Callable[..., Any] = None,
|
|
56
|
+
sni_use_default_callback_function: bool = False,
|
|
57
|
+
sni_use_default_callback_function_extended: bool = False,
|
|
58
|
+
sni_add_new_domains_to_default_server_certificate: bool = False,
|
|
59
|
+
sni_create_server_certificate_for_each_domain: bool = False,
|
|
60
|
+
sni_server_certificates_cache_directory: str = None,
|
|
61
|
+
sni_get_server_certificate_from_server_socket: bool = False,
|
|
62
|
+
sni_server_certificate_from_server_socket_download_directory: str = None,
|
|
63
|
+
skip_extension_id_list: list = None,
|
|
64
|
+
custom_server_certificate_usage: bool = False,
|
|
65
|
+
custom_server_certificate_path: str = None,
|
|
66
|
+
custom_private_key_path: str = None,
|
|
67
|
+
get_process_name: bool = False,
|
|
68
|
+
ssh_user: str = None,
|
|
69
|
+
ssh_pass: str = None,
|
|
70
|
+
ssh_script_to_execute: Union[
|
|
71
|
+
Literal['process_from_port'],
|
|
72
|
+
None
|
|
73
|
+
] = None,
|
|
74
|
+
logs_directory: str = None,
|
|
75
|
+
logger_name: str = 'SocketWrapper',
|
|
76
|
+
logger_queue: multiprocessing.Queue = None,
|
|
77
|
+
statistics_logger_name: str = 'statistics',
|
|
78
|
+
statistics_logger_queue: multiprocessing.Queue = None,
|
|
79
|
+
exceptions_logger_name: str = 'SocketWrapperExceptions',
|
|
80
|
+
exceptions_logger_queue: multiprocessing.Queue = None,
|
|
81
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
|
|
82
|
+
sslkeylog_file_path: str = None,
|
|
83
|
+
print_kwargs: dict = None,
|
|
21
84
|
):
|
|
85
|
+
"""
|
|
86
|
+
Socket Wrapper class that will be used to create sockets, listen on them, accept connections and send them to
|
|
87
|
+
new threads.
|
|
88
|
+
|
|
89
|
+
:param ca_certificate_name: CA certificate name.
|
|
90
|
+
:param ca_certificate_filepath: CA certificate file path with '.pem' extension.
|
|
91
|
+
:param ca_certificate_crt_filepath: CA certificate file path with '.crt' extension.
|
|
92
|
+
This file will be created from the PEM file 'ca_certificate_filepath' for manual installation.
|
|
93
|
+
:param install_ca_certificate_to_root_store: boolean, if True, CA certificate will be installed
|
|
94
|
+
to the root store.
|
|
95
|
+
:param uninstall_unused_ca_certificates_with_ca_certificate_name: boolean, if True, unused CA certificates
|
|
96
|
+
with provided 'ca_certificate_name' will be uninstalled.
|
|
97
|
+
:param default_server_certificate_usage: boolean, if True, default server certificate will be used
|
|
98
|
+
for each incoming socket.
|
|
99
|
+
:param sni_custom_callback_function: callable, custom callback function that will be executed when
|
|
100
|
+
there is a SNI present in the request.
|
|
101
|
+
|
|
102
|
+
Example: custom callback function to set the 'server_hostname' for the socket with the domain name from SNI:
|
|
103
|
+
def sni_handle(
|
|
104
|
+
sni_ssl_socket: ssl.SSLSocket,
|
|
105
|
+
sni_destination_name: str,
|
|
106
|
+
sni_ssl_context: ssl.SSLContext):
|
|
107
|
+
# Set 'server_hostname' for the socket.
|
|
108
|
+
sni_ssl_socket.server_hostname = sni_destination_name
|
|
109
|
+
|
|
110
|
+
return sni_handle
|
|
111
|
+
|
|
112
|
+
The function should accept 3 arguments:
|
|
113
|
+
sni_ssl_socket: ssl.SSLSocket, SSL socket object.
|
|
114
|
+
sni_destination_name: string, domain name from SNI.
|
|
115
|
+
sni_ssl_context: ssl.SSLContext, SSL context object.
|
|
116
|
+
|
|
117
|
+
These parameters are default for any SNI handler function, so you can use them in your custom function.
|
|
118
|
+
|
|
119
|
+
:param sni_use_default_callback_function: boolean, if True, default callback function will be used.
|
|
120
|
+
The function will set the 'server_hostname' for the socket with the domain name from SNI.
|
|
121
|
+
The example in 'sni_custom_callback_function' parameter is the function that will be used.
|
|
122
|
+
:param sni_use_default_callback_function_extended: boolean, if True, default callback function will be used
|
|
123
|
+
with extended functionality. This feature will handle all the features and parameters that are set in
|
|
124
|
+
the SocketWrapper object that are related to SNI. THis includes certificate management for each domain,
|
|
125
|
+
adding new domains to the default certificate, creating new certificates for each domain, etc.
|
|
126
|
+
This feature also utilizes the 'request_domain_queue' parameter to get the domain name that was requested
|
|
127
|
+
from the DNS server (atomicshop.wrappers.socketw.dns_server).
|
|
128
|
+
:param sni_add_new_domains_to_default_server_certificate: boolean, if True, new domains that hit the tcp
|
|
129
|
+
server will be added to default server certificate.
|
|
130
|
+
:param sni_create_server_certificate_for_each_domain: boolean, if True, server certificate will be
|
|
131
|
+
created and used for each domain that hit the tcp server.
|
|
132
|
+
:param sni_get_server_certificate_from_server_socket: boolean, if True, server certificate will be
|
|
133
|
+
downloaded from the server socket.
|
|
134
|
+
:param sni_server_certificate_from_server_socket_download_directory: string, path to directory where
|
|
135
|
+
server certificate will be downloaded from the server socket.
|
|
136
|
+
:param default_server_certificate_name: default server certificate name.
|
|
137
|
+
:param default_certificate_domain_list: list of string, domains to create the default certificate with.
|
|
138
|
+
:param default_server_certificate_directory: string, path to directory where default certificate file
|
|
139
|
+
will be stored.
|
|
140
|
+
:param sni_server_certificates_cache_directory: string, path to directory where all server certificates for
|
|
141
|
+
each domain will be created.
|
|
142
|
+
:param skip_extension_id_list: list of string, list of extension IDs that will be skipped when processing
|
|
143
|
+
the certificate from the server socket.
|
|
144
|
+
Example: ['1.3.6.1.5.5.7.3.2', '2.5.29.31', '1.3.6.1.5.5.7.1.1']
|
|
145
|
+
:param custom_server_certificate_usage: boolean, if True, custom server certificate will be used.
|
|
146
|
+
:param custom_server_certificate_path: string, path to custom server certificate.
|
|
147
|
+
:param custom_private_key_path: string, path to custom private key.
|
|
148
|
+
server certificates from the server socket.
|
|
149
|
+
:param get_process_name: boolean, if the process name and command line should be gathered from the socket.
|
|
150
|
+
If the socket came from remote host we will try ti get the process name from the remote host by SSH.
|
|
151
|
+
By default, we don't get the process name, because we're using psutil to get the process name and command
|
|
152
|
+
line, but if the process is protected by the system, then command line will be empty.
|
|
153
|
+
It's up to user to decide if to run the script with root privileges or not, this is only relevant if
|
|
154
|
+
the script is running on the same host.
|
|
155
|
+
:param ssh_user: string, SSH username that will be used to connect to remote host.
|
|
156
|
+
:param ssh_pass: string, SSH password that will be used to connect to remote host.
|
|
157
|
+
:param ssh_script_to_execute: string, script that will be executed to get the process name on ssh remote host.
|
|
158
|
+
:param logs_directory: string, path to directory where daily statistics.csv files and all the other logger
|
|
159
|
+
files will be stored. After you initialize the SocketWrapper object, you can get the statistics_writer
|
|
160
|
+
object from it and use it to write statistics to the file in a worker thread.
|
|
161
|
+
|
|
162
|
+
socket_wrapper_instance = SocketWrapper(...)
|
|
163
|
+
statistics_writer = socket_wrapper_instance.statistics_writer
|
|
164
|
+
|
|
165
|
+
statistics_writer: statistics_csv.StatisticsCSVWriter object, there is a logger object that
|
|
166
|
+
will be used to write the statistics file.
|
|
167
|
+
:param logger_name: string, name of the logger that will be used to log messages.
|
|
168
|
+
:param logger_queue: multiprocessing.Queue, queue that will be used to log messages in multiprocessing.
|
|
169
|
+
You need to start the logger listener in the main process to handle the queue.
|
|
170
|
+
:param statistics_logger_name: string, name of the logger that will be used to log statistics.
|
|
171
|
+
:param statistics_logger_queue: multiprocessing.Queue, queue that will be used to log statistics in
|
|
172
|
+
multiprocessing. You need to start the logger listener in the main process to handle the queue.
|
|
173
|
+
:param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
|
|
174
|
+
:param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
|
|
175
|
+
multiprocessing. You need to start the logger listener in the main process to handle the queue.
|
|
176
|
+
:param enable_sslkeylogfile_env_to_client_ssl_context: boolean, if True, each client SSL context
|
|
177
|
+
that will be created by the SocketWrapper will have save the SSL handshake keys to the file
|
|
178
|
+
defined in 'sslkeylog_file_path' parameter.
|
|
179
|
+
:param sslkeylog_file_path: string, path to file where SSL handshake keys will be saved.
|
|
180
|
+
If not provided and 'enable_sslkeylogfile_env_to_client_ssl_context' is True, then
|
|
181
|
+
the environment variable 'SSLKEYLOGFILE' will be used.
|
|
182
|
+
:param print_kwargs: dict, additional arguments to pass to the print function.
|
|
183
|
+
"""
|
|
22
184
|
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
185
|
+
self.ip_address: str = ip_address
|
|
186
|
+
self.port: int = port
|
|
187
|
+
self.engine: initialize_engines.ModuleCategory = engine
|
|
188
|
+
self.ca_certificate_name: str = ca_certificate_name
|
|
189
|
+
self.ca_certificate_filepath: str = ca_certificate_filepath
|
|
190
|
+
self.ca_certificate_crt_filepath: str = ca_certificate_crt_filepath
|
|
191
|
+
self.install_ca_certificate_to_root_store: bool = install_ca_certificate_to_root_store
|
|
192
|
+
self.uninstall_unused_ca_certificates_with_ca_certificate_name: bool = \
|
|
193
|
+
uninstall_unused_ca_certificates_with_ca_certificate_name
|
|
194
|
+
self.default_server_certificate_usage: bool = default_server_certificate_usage
|
|
195
|
+
self.default_server_certificate_name: str = default_server_certificate_name
|
|
196
|
+
self.default_certificate_domain_list: list = default_certificate_domain_list
|
|
197
|
+
self.default_server_certificate_directory: str = default_server_certificate_directory
|
|
198
|
+
self.sni_custom_callback_function: Callable[..., Any] = sni_custom_callback_function
|
|
199
|
+
self.sni_use_default_callback_function: bool = sni_use_default_callback_function
|
|
200
|
+
self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
|
|
201
|
+
self.sni_add_new_domains_to_default_server_certificate: bool = sni_add_new_domains_to_default_server_certificate
|
|
202
|
+
self.sni_create_server_certificate_for_each_domain: bool = sni_create_server_certificate_for_each_domain
|
|
203
|
+
self.sni_server_certificates_cache_directory: str = sni_server_certificates_cache_directory
|
|
204
|
+
self.sni_get_server_certificate_from_server_socket: bool = sni_get_server_certificate_from_server_socket
|
|
205
|
+
self.sni_server_certificate_from_server_socket_download_directory: str = \
|
|
206
|
+
sni_server_certificate_from_server_socket_download_directory
|
|
207
|
+
self.skip_extension_id_list: list = skip_extension_id_list
|
|
208
|
+
self.custom_server_certificate_usage: bool = custom_server_certificate_usage
|
|
209
|
+
self.custom_server_certificate_path: str = custom_server_certificate_path
|
|
210
|
+
self.custom_private_key_path: str = custom_private_key_path
|
|
211
|
+
self.get_process_name: bool = get_process_name
|
|
212
|
+
self.ssh_user: str = ssh_user
|
|
213
|
+
self.ssh_pass: str = ssh_pass
|
|
214
|
+
self.ssh_script_to_execute = ssh_script_to_execute
|
|
215
|
+
self.forwarding_dns_service_ipv4_list___only_for_localhost = (
|
|
216
|
+
forwarding_dns_service_ipv4_list___only_for_localhost)
|
|
217
|
+
self.enable_sslkeylogfile_env_to_client_ssl_context: bool = (
|
|
218
|
+
enable_sslkeylogfile_env_to_client_ssl_context)
|
|
219
|
+
self.sslkeylog_file_path: str = sslkeylog_file_path
|
|
220
|
+
self.print_kwargs: dict = print_kwargs
|
|
32
221
|
|
|
33
222
|
self.socket_object = None
|
|
34
223
|
|
|
@@ -38,7 +227,6 @@ class SocketWrapper:
|
|
|
38
227
|
|
|
39
228
|
self.sni_received_dict: dict = dict()
|
|
40
229
|
self.sni_execute_extended: bool = False
|
|
41
|
-
self.requested_domain_from_dns_server = None
|
|
42
230
|
self.certauth_wrapper = None
|
|
43
231
|
|
|
44
232
|
# Defining list of threads, so we can "join()" them in the end all at once.
|
|
@@ -47,11 +235,214 @@ class SocketWrapper:
|
|
|
47
235
|
# Defining listening sockets list, which will be used with "select" library in 'loop_for_incoming_sockets'.
|
|
48
236
|
self.listening_sockets: list = list()
|
|
49
237
|
|
|
50
|
-
# Defining '
|
|
238
|
+
# Defining 'ssh_script_string' variable, which will be used to process SSH scripts.
|
|
51
239
|
self.ssh_script_processor = None
|
|
52
|
-
if self.
|
|
53
|
-
|
|
54
|
-
|
|
240
|
+
if self.get_process_name:
|
|
241
|
+
# noinspection PyTypeChecker
|
|
242
|
+
self.package_processor: package_mains_processor.PackageMainsProcessor | None = package_mains_processor.PackageMainsProcessor(script_file_stem=self.ssh_script_to_execute)
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
self.package_processor = None
|
|
246
|
+
|
|
247
|
+
# We will initialize it during the first 'get_process_name' function call.
|
|
248
|
+
self.ssh_client: SSHRemote | None = None
|
|
249
|
+
|
|
250
|
+
# If logs directory was not set, we will use the working directory.
|
|
251
|
+
if not logs_directory:
|
|
252
|
+
logs_directory = str(Path.cwd() / LOGS_DIRECTORY_NAME)
|
|
253
|
+
self.logs_directory: str = logs_directory
|
|
254
|
+
|
|
255
|
+
if not logger_name:
|
|
256
|
+
logger_name = 'SocketWrapper'
|
|
257
|
+
self.logger_name: str = logger_name
|
|
258
|
+
self.logger_name_listener: str = f"{logger_name}.listener"
|
|
259
|
+
|
|
260
|
+
if loggingw.is_logger_exists(self.logger_name_listener):
|
|
261
|
+
self.logger = loggingw.get_logger_with_level(self.logger_name_listener)
|
|
262
|
+
elif not logger_queue:
|
|
263
|
+
_ = loggingw.create_logger(
|
|
264
|
+
logger_name=logger_name,
|
|
265
|
+
directory_path=self.logs_directory,
|
|
266
|
+
add_stream=True,
|
|
267
|
+
add_timedfile_with_internal_queue=True,
|
|
268
|
+
formatter_streamhandler='DEFAULT',
|
|
269
|
+
formatter_filehandler='DEFAULT'
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
self.logger = loggingw.get_logger_with_level(self.logger_name_listener)
|
|
273
|
+
else:
|
|
274
|
+
_ = loggingw.create_logger(
|
|
275
|
+
logger_name=logger_name,
|
|
276
|
+
add_queue_handler=True,
|
|
277
|
+
log_queue=logger_queue
|
|
278
|
+
)
|
|
279
|
+
self.logger = loggingw.get_logger_with_level(self.logger_name_listener)
|
|
280
|
+
|
|
281
|
+
self.statistics_writer = statistics_csv.StatisticsCSVWriter(
|
|
282
|
+
logger_name=statistics_logger_name,
|
|
283
|
+
directory_path=self.logs_directory,
|
|
284
|
+
log_queue=statistics_logger_queue,
|
|
285
|
+
add_queue_handler_no_listener_multiprocessing=True
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if not exceptions_logger_name:
|
|
289
|
+
exceptions_logger_name = 'SocketWrapperExceptions'
|
|
290
|
+
|
|
291
|
+
self.exceptions_logger = loggingw.ExceptionCsvLogger(
|
|
292
|
+
logger_name=exceptions_logger_name,
|
|
293
|
+
directory_path=self.logs_directory,
|
|
294
|
+
log_queue=exceptions_logger_queue,
|
|
295
|
+
add_queue_handler_no_listener_multiprocessing=True
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
self.test_config()
|
|
299
|
+
|
|
300
|
+
def test_config(self):
|
|
301
|
+
if self.sni_custom_callback_function and (
|
|
302
|
+
self.sni_use_default_callback_function or self.sni_use_default_callback_function_extended):
|
|
303
|
+
message = "You can't use both custom and default SNI function at the same time."
|
|
304
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
305
|
+
|
|
306
|
+
if self.sni_use_default_callback_function_extended and not self.sni_use_default_callback_function:
|
|
307
|
+
message = "You can't use extended SNI function without default SNI function."
|
|
308
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
309
|
+
|
|
310
|
+
if self.sni_use_default_callback_function and self.sni_custom_callback_function:
|
|
311
|
+
message = \
|
|
312
|
+
"You can't set both [sni_use_default_callback_function = True] and [sni_custom_callback_function]."
|
|
313
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
booleans.is_only_1_true_in_list(
|
|
317
|
+
booleans_list_of_tuples=[
|
|
318
|
+
(self.default_server_certificate_usage, 'default_server_certificate_usage'),
|
|
319
|
+
(self.sni_create_server_certificate_for_each_domain,
|
|
320
|
+
'sni_create_server_certificate_for_each_domain'),
|
|
321
|
+
(self.custom_server_certificate_usage, 'custom_server_certificate_usage')
|
|
322
|
+
],
|
|
323
|
+
raise_if_all_false=True
|
|
324
|
+
)
|
|
325
|
+
except ValueError as e:
|
|
326
|
+
raise SocketWrapperConfigurationValuesError(str(e))
|
|
327
|
+
|
|
328
|
+
if not self.default_server_certificate_usage and \
|
|
329
|
+
self.sni_add_new_domains_to_default_server_certificate:
|
|
330
|
+
message = "No point setting [sni_add_new_domains_to_default_server_certificate = True]\n" \
|
|
331
|
+
"If you're not going to use default certificates [default_server_certificate_usage = False]"
|
|
332
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
333
|
+
|
|
334
|
+
if self.sni_get_server_certificate_from_server_socket and \
|
|
335
|
+
not self.sni_create_server_certificate_for_each_domain:
|
|
336
|
+
message = "You set [sni_get_server_certificate_from_server_socket = True],\n" \
|
|
337
|
+
"But you didn't set [sni_create_server_certificate_for_each_domain = True]."
|
|
338
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
339
|
+
|
|
340
|
+
if self.custom_server_certificate_usage and \
|
|
341
|
+
not self.custom_server_certificate_path:
|
|
342
|
+
message = "You set [custom_server_certificate_usage = True],\n" \
|
|
343
|
+
"But you didn't set [custom_server_certificate_path]."
|
|
344
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
345
|
+
|
|
346
|
+
# If 'custom_certificate_usage' was set to 'True'.
|
|
347
|
+
if self.custom_server_certificate_usage:
|
|
348
|
+
# Check file existence.
|
|
349
|
+
if not filesystem.is_file_exists(file_path=self.custom_server_certificate_path):
|
|
350
|
+
message = f"File not found: {self.custom_server_certificate_path}"
|
|
351
|
+
print_api(message, color='red', logger=self.logger)
|
|
352
|
+
return 1
|
|
353
|
+
|
|
354
|
+
# And if 'custom_private_key_path' field was populated in [advanced] section, we'll check its existence.
|
|
355
|
+
if self.custom_private_key_path:
|
|
356
|
+
# Check private key file existence.
|
|
357
|
+
if not filesystem.is_file_exists(file_path=self.custom_private_key_path):
|
|
358
|
+
message = f"File not found: {self.custom_private_key_path}"
|
|
359
|
+
print_api(message, color='red', logger=self.logger)
|
|
360
|
+
return 1
|
|
361
|
+
|
|
362
|
+
# Checking if listening address is in use.
|
|
363
|
+
listening_check_list = [f"{self.ip_address}:{self.port}"]
|
|
364
|
+
port_in_use = psutil_networks.get_processes_using_port_list(listening_check_list)
|
|
365
|
+
if port_in_use:
|
|
366
|
+
error_messages: list = list()
|
|
367
|
+
for port, process_info in port_in_use.items():
|
|
368
|
+
error_messages.append(f"Port [{port}] is already in use by process: {process_info}")
|
|
369
|
+
raise SocketWrapperPortInUseError("\n".join(error_messages))
|
|
370
|
+
|
|
371
|
+
if not filesystem.is_file_exists(file_path=self.ca_certificate_filepath):
|
|
372
|
+
# Initialize CertAuthWrapper.
|
|
373
|
+
ca_certificate_directory: str = str(Path(self.ca_certificate_filepath).parent)
|
|
374
|
+
certauth_wrapper = certauthw.CertAuthWrapper(
|
|
375
|
+
ca_certificate_name=self.ca_certificate_name,
|
|
376
|
+
ca_certificate_filepath=self.ca_certificate_filepath,
|
|
377
|
+
server_certificate_directory=ca_certificate_directory
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Create CA certificate if it doesn't exist.
|
|
381
|
+
certauth_wrapper.create_use_ca_certificate()
|
|
382
|
+
|
|
383
|
+
certificates.write_crt_certificate_file_in_pem_format_from_pem_file(
|
|
384
|
+
pem_file_path=self.ca_certificate_filepath,
|
|
385
|
+
crt_file_path=self.ca_certificate_crt_filepath)
|
|
386
|
+
|
|
387
|
+
# If someone removed the CA certificate file manually, and now it was created, we also need to
|
|
388
|
+
# clear the cached certificates.
|
|
389
|
+
try:
|
|
390
|
+
shutil.rmtree(self.sni_server_certificates_cache_directory)
|
|
391
|
+
# If the directory doesn't exist it will throw an exception, which is OK.
|
|
392
|
+
except FileNotFoundError:
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
|
|
396
|
+
print_api("Removed cached server certificates.", logger=self.logger)
|
|
397
|
+
else:
|
|
398
|
+
os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
|
|
399
|
+
|
|
400
|
+
if self.install_ca_certificate_to_root_store:
|
|
401
|
+
if not self.ca_certificate_filepath:
|
|
402
|
+
message = "You set [install_ca_certificate_to_root_store = True],\n" \
|
|
403
|
+
"But you didn't set [ca_certificate_filepath]."
|
|
404
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
405
|
+
|
|
406
|
+
# Before installation check if there are any unused certificates with the same name.
|
|
407
|
+
if self.uninstall_unused_ca_certificates_with_ca_certificate_name:
|
|
408
|
+
# Check how many certificates with our ca certificate name are installed.
|
|
409
|
+
is_installed_by_name, certificate_list_by_name = certificates.is_certificate_in_store(
|
|
410
|
+
issuer_name=self.ca_certificate_name)
|
|
411
|
+
# If there is more than one certificate with the same name, delete them all.
|
|
412
|
+
if is_installed_by_name and len(certificate_list_by_name) > 1:
|
|
413
|
+
message = f"More than one certificate with the same issuer name is installed. Removing all..."
|
|
414
|
+
print_api(message, color='yellow', logger=self.logger)
|
|
415
|
+
certificates.delete_certificate_by_issuer_name(self.ca_certificate_name)
|
|
416
|
+
# If there is only one certificate with the same name, check if it is the same certificate.
|
|
417
|
+
elif is_installed_by_name and len(certificate_list_by_name) == 1:
|
|
418
|
+
is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
|
|
419
|
+
certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
|
|
420
|
+
print_kwargs=self.print_kwargs)
|
|
421
|
+
# If the certificate is not the same, delete it.
|
|
422
|
+
if not is_installed_by_file:
|
|
423
|
+
if not permissions.is_admin():
|
|
424
|
+
raise SocketWrapperConfigurationValuesError(
|
|
425
|
+
"You need to run the script with admin rights to uninstall the unused certificates.")
|
|
426
|
+
message = (
|
|
427
|
+
f"Certificate with the same issuer name is installed, but it is not the same certificate. "
|
|
428
|
+
f"Removing...")
|
|
429
|
+
print_api(message, color='yellow', logger=self.logger)
|
|
430
|
+
certificates.delete_certificate_by_issuer_name(
|
|
431
|
+
self.ca_certificate_name, store_location="ROOT", print_kwargs={'logger': self.logger})
|
|
432
|
+
|
|
433
|
+
if self.install_ca_certificate_to_root_store:
|
|
434
|
+
# Install CA certificate to the root store if it is not installed.
|
|
435
|
+
is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
|
|
436
|
+
certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
|
|
437
|
+
print_kwargs=self.print_kwargs
|
|
438
|
+
)
|
|
439
|
+
if not is_installed_by_file:
|
|
440
|
+
if not permissions.is_admin():
|
|
441
|
+
raise SocketWrapperConfigurationValuesError(
|
|
442
|
+
"You need to run the script with admin rights to install the CA certificate.")
|
|
443
|
+
certificates.install_certificate_file(
|
|
444
|
+
self.ca_certificate_filepath, store_location="ROOT",
|
|
445
|
+
print_kwargs={'logger': self.logger, 'color': 'blue'})
|
|
55
446
|
|
|
56
447
|
# Creating listening sockets.
|
|
57
448
|
def create_socket_ipv4_tcp(self, ip_address: str, port: int):
|
|
@@ -61,127 +452,267 @@ class SocketWrapper:
|
|
|
61
452
|
creator.bind_socket_with_ip_port(self.socket_object, ip_address, port, logger=self.logger)
|
|
62
453
|
creator.set_listen_on_socket(self.socket_object, logger=self.logger)
|
|
63
454
|
|
|
64
|
-
# self.socket_object, accept_error_message = creator.wrap_socket_with_ssl_context_server_sni_extended(
|
|
65
|
-
# self.socket_object, config=self.config, print_kwargs={'logger': self.logger})
|
|
66
|
-
|
|
67
455
|
return self.socket_object
|
|
68
456
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
thread_current = threading.Thread(target=thread_function_name, args=(*reference_args,))
|
|
85
|
-
thread_current.daemon = True
|
|
86
|
-
thread_current.start()
|
|
87
|
-
# Append to list of threads, so they can be "joined" later
|
|
88
|
-
self.threads_list.append(thread_current)
|
|
457
|
+
def start_listening_socket(
|
|
458
|
+
self,
|
|
459
|
+
callable_function: Callable[..., Any],
|
|
460
|
+
callable_args: tuple = ()
|
|
461
|
+
):
|
|
462
|
+
"""
|
|
463
|
+
Start listening on a single socket with given IP address and port.
|
|
464
|
+
This function is used to start listening on a single socket, for example, when you want to listen on a specific
|
|
465
|
+
IP address and port.
|
|
466
|
+
|
|
467
|
+
:param callable_function: callable, function that you want to execute when client
|
|
468
|
+
socket received by 'accept()' and connection has been made.
|
|
469
|
+
:param callable_args: tuple, that will be passed to 'callable_function' when it will be called.
|
|
470
|
+
:return: None
|
|
471
|
+
"""
|
|
89
472
|
|
|
90
|
-
|
|
91
|
-
|
|
473
|
+
if self.engine:
|
|
474
|
+
acceptor_name: str = f"acceptor-{self.engine.engine_name}-{self.ip_address}:{self.port}"
|
|
475
|
+
else:
|
|
476
|
+
acceptor_name: str = f"acceptor-{self.ip_address}:{self.port}"
|
|
92
477
|
|
|
93
|
-
self.
|
|
478
|
+
socket_by_port = self.create_socket_ipv4_tcp(self.ip_address, self.port)
|
|
479
|
+
threading.Thread(
|
|
480
|
+
target=self.listening_socket_loop,
|
|
481
|
+
args=(socket_by_port, callable_function, callable_args),
|
|
482
|
+
name=acceptor_name,
|
|
483
|
+
daemon=True
|
|
484
|
+
).start()
|
|
94
485
|
|
|
95
|
-
def
|
|
96
|
-
self,
|
|
97
|
-
|
|
486
|
+
def listening_socket_loop(
|
|
487
|
+
self,
|
|
488
|
+
listening_socket_object: socket.socket,
|
|
489
|
+
callable_function: Callable[..., Any],
|
|
490
|
+
callable_args=()
|
|
491
|
+
):
|
|
98
492
|
"""
|
|
99
493
|
Loop to wait for new connections, accept them and send to new threads.
|
|
100
494
|
The boolean variable was declared True in the beginning of the script and will be set to False if the process
|
|
101
495
|
will be killed or closed.
|
|
102
496
|
|
|
103
|
-
:param
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
:param
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
497
|
+
:param listening_socket_object: listening socket that was created with bind.
|
|
498
|
+
:param callable_function: callable, function that you want to execute when client
|
|
499
|
+
socket received by 'accept()' and connection has been made.
|
|
500
|
+
:param callable_args: tuple, that will be passed to 'function_reference' when it will be called.
|
|
501
|
+
Your function should be able to accept these arguments before the 'callable_args' tuple:
|
|
502
|
+
(client_socket, process_name, is_tls, domain_from_dns_server).
|
|
503
|
+
Meaning that 'callable_args' will be added to the end of the arguments tuple like so:
|
|
504
|
+
(client_socket, process_name, is_tls, tls_type, tls_version, domain_from_dns_server,
|
|
505
|
+
*callable_args).
|
|
506
|
+
|
|
507
|
+
client_socket: socket, client socket that was accepted.
|
|
508
|
+
process_name: string, process name that was gathered from the socket.
|
|
509
|
+
is_tls: boolean, if the socket is SSL/TLS.
|
|
510
|
+
domain_from_dns_server: string, domain that was requested from DNS server.
|
|
111
511
|
:return:
|
|
112
512
|
"""
|
|
113
513
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
514
|
+
listening_sockets: list = [listening_socket_object]
|
|
515
|
+
|
|
516
|
+
while True:
|
|
517
|
+
engine_name: str = ''
|
|
518
|
+
source_ip: str = ''
|
|
519
|
+
source_hostname: str = ''
|
|
520
|
+
dest_port: int = 0
|
|
521
|
+
process_name: str = ''
|
|
522
|
+
domain_from_engine: str = ''
|
|
118
523
|
|
|
119
|
-
# Socket accept infinite loop run variable. When the process is closed, the loop will break and the threads will
|
|
120
|
-
# be joined and garbage collection cleaned if there is any
|
|
121
|
-
socket_infinite_loop_run: bool = True
|
|
122
|
-
while socket_infinite_loop_run:
|
|
123
524
|
try:
|
|
124
525
|
# Using "select.select" which is currently the only API function that works on all
|
|
125
526
|
# operating system types: Windows / Linux / BSD.
|
|
126
527
|
# To accept connection, we don't need "writable" and "exceptional", since "readable" holds the currently
|
|
127
528
|
# connected socket.
|
|
128
|
-
readable, writable, exceptional = select.select(
|
|
529
|
+
readable, writable, exceptional = select.select(listening_sockets, [], [])
|
|
129
530
|
listening_socket_object = readable[0]
|
|
130
531
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
532
|
+
listening_ip, listening_port = listening_socket_object.getsockname()
|
|
533
|
+
|
|
534
|
+
# Get the domain to connect on this process in case on no SNI provided.
|
|
535
|
+
for domain, ip_port_dict in self.engine.domain_target_dict.items():
|
|
536
|
+
if ip_port_dict['ip'] == listening_ip:
|
|
537
|
+
domain_from_engine = domain
|
|
538
|
+
break
|
|
539
|
+
# If there was no domain found, try to find the IP address for port.
|
|
540
|
+
if not domain_from_engine:
|
|
541
|
+
for port, file_or_ip in self.engine.port_target_dict.items():
|
|
542
|
+
if file_or_ip['ip'] == listening_ip:
|
|
543
|
+
# Get the value from the 'on_port_connect' dictionary.
|
|
544
|
+
address_or_file_path: str = self.engine.on_port_connect[str(listening_port)]
|
|
545
|
+
ip_port_address_from_config: tuple = initialize_engines.get_ipv4_from_engine_on_connect_port(
|
|
546
|
+
address_or_file_path)
|
|
547
|
+
if not ip_port_address_from_config:
|
|
548
|
+
raise ValueError(
|
|
549
|
+
f"Invalid IP address or file path in 'on_port_connect' for port "
|
|
550
|
+
f"{listening_port}: {address_or_file_path}"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
domain_from_engine = ip_port_address_from_config[0]
|
|
554
|
+
|
|
555
|
+
break
|
|
556
|
+
|
|
557
|
+
self.logger.info(f"Requested domain setting: {domain_from_engine}")
|
|
558
|
+
|
|
559
|
+
engine_name = get_engine_name(domain_from_engine, [self.engine])
|
|
137
560
|
|
|
138
561
|
# Wait from any connection on "accept()".
|
|
139
562
|
# 'client_socket' is socket or ssl socket, 'client_address' is a tuple (ip_address, port).
|
|
140
563
|
client_socket, client_address, accept_error_message = accepter.accept_connection_with_error(
|
|
141
|
-
listening_socket_object,
|
|
564
|
+
listening_socket_object, domain_from_dns_server=domain_from_engine,
|
|
565
|
+
print_kwargs={'logger': self.logger})
|
|
566
|
+
|
|
567
|
+
source_ip: str = client_address[0]
|
|
568
|
+
source_port: int = client_address[1]
|
|
569
|
+
dest_port: int = listening_socket_object.getsockname()[1]
|
|
570
|
+
|
|
571
|
+
message: str = f"Accepted connection from [{source_ip}:{source_port}] to [{listening_ip}:{dest_port}] | domain: {domain_from_engine}"
|
|
572
|
+
print_api(message, logger=self.logger)
|
|
573
|
+
|
|
574
|
+
# Not always there will be a hostname resolved by the IP address, so we will leave it empty if it fails.
|
|
575
|
+
try:
|
|
576
|
+
source_hostname = socket.gethostbyaddr(source_ip)[0]
|
|
577
|
+
source_hostname = source_hostname.lower()
|
|
578
|
+
except socket.herror:
|
|
579
|
+
pass
|
|
142
580
|
|
|
143
581
|
# This is the earliest stage to ask for process name.
|
|
144
582
|
# SSH Remote / LOCALHOST script execution to identify process section.
|
|
145
|
-
# If '
|
|
146
|
-
|
|
147
|
-
|
|
583
|
+
# If 'get_process_name' was set to True, then this will be executed.
|
|
584
|
+
if self.get_process_name:
|
|
585
|
+
# Initializing SSHRemote class if not initialized.
|
|
586
|
+
if self.ssh_client is None:
|
|
587
|
+
self.ssh_client = SSHRemote(
|
|
588
|
+
ip_address=source_ip, username=self.ssh_user, password=self.ssh_pass, logger=self.logger)
|
|
589
|
+
|
|
148
590
|
# Get the process name from the socket.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
591
|
+
get_command_instance = process_getter.GetCommandLine(
|
|
592
|
+
client_ip=source_ip,
|
|
593
|
+
client_port=source_port,
|
|
594
|
+
package_processor=self.package_processor,
|
|
595
|
+
ssh_client=self.ssh_client,
|
|
596
|
+
logger=self.logger)
|
|
597
|
+
process_name = get_command_instance.get_process_name(print_kwargs={'logger': self.logger})
|
|
598
|
+
|
|
599
|
+
# from ..pywin32w.win_event_log import fetch
|
|
600
|
+
# events = fetch.get_latest_events(
|
|
601
|
+
# server_ip=source_ip,
|
|
602
|
+
# username=self.ssh_user,
|
|
603
|
+
# password=self.ssh_pass,
|
|
604
|
+
# log_name='Security',
|
|
605
|
+
# count=50,
|
|
606
|
+
# event_id_list=[5156]
|
|
607
|
+
# )
|
|
608
|
+
#
|
|
609
|
+
# source_port = client_address[1]
|
|
610
|
+
# for event in events:
|
|
611
|
+
# if source_port == event['StringsDict']['Source Port']:
|
|
612
|
+
# process_name = event['StringsDict']['Application Name']
|
|
613
|
+
# break
|
|
614
|
+
#
|
|
615
|
+
# if process_name == '':
|
|
616
|
+
# raise RuntimeError("Failed to get process name from the remote host via Event Log.")
|
|
152
617
|
|
|
153
618
|
# If 'accept()' function worked well, SSL worked well, then 'client_socket' won't be empty.
|
|
154
619
|
if client_socket:
|
|
155
620
|
# Get the protocol type from the socket.
|
|
156
621
|
is_tls: bool = False
|
|
157
|
-
|
|
622
|
+
|
|
623
|
+
try:
|
|
624
|
+
tls_properties = ssl_base.is_tls(client_socket, timeout=1)
|
|
625
|
+
except TimeoutError:
|
|
626
|
+
error: str = "TimeoutError: TLS detection timed out. Dropping accepted socket."
|
|
627
|
+
self.logger.error(error)
|
|
628
|
+
|
|
629
|
+
self.statistics_writer.write_accept_error(
|
|
630
|
+
engine=engine_name,
|
|
631
|
+
source_host=source_hostname,
|
|
632
|
+
source_ip=source_ip,
|
|
633
|
+
error_message=error,
|
|
634
|
+
dest_port=str(dest_port),
|
|
635
|
+
host=domain_from_engine,
|
|
636
|
+
process_name=process_name)
|
|
637
|
+
|
|
638
|
+
client_socket.close()
|
|
639
|
+
continue
|
|
640
|
+
|
|
158
641
|
if tls_properties:
|
|
159
642
|
is_tls = True
|
|
643
|
+
tls_type, tls_version = tls_properties
|
|
644
|
+
else:
|
|
645
|
+
tls_type, tls_version = None, None
|
|
160
646
|
|
|
161
647
|
# If 'is_tls' is True.
|
|
162
648
|
ssl_client_socket = None
|
|
163
649
|
if is_tls:
|
|
650
|
+
sni_handler = sni.SNISetup(
|
|
651
|
+
default_server_certificate_usage=self.default_server_certificate_usage,
|
|
652
|
+
default_server_certificate_name=self.default_server_certificate_name,
|
|
653
|
+
default_certificate_domain_list=self.default_certificate_domain_list,
|
|
654
|
+
default_server_certificate_directory=self.default_server_certificate_directory,
|
|
655
|
+
sni_custom_callback_function=self.sni_custom_callback_function,
|
|
656
|
+
sni_use_default_callback_function=self.sni_use_default_callback_function,
|
|
657
|
+
sni_use_default_callback_function_extended=self.sni_use_default_callback_function_extended,
|
|
658
|
+
sni_add_new_domains_to_default_server_certificate=(
|
|
659
|
+
self.sni_add_new_domains_to_default_server_certificate),
|
|
660
|
+
sni_server_certificates_cache_directory=self.sni_server_certificates_cache_directory,
|
|
661
|
+
sni_create_server_certificate_for_each_domain=(
|
|
662
|
+
self.sni_create_server_certificate_for_each_domain),
|
|
663
|
+
sni_get_server_certificate_from_server_socket=(
|
|
664
|
+
self.sni_get_server_certificate_from_server_socket),
|
|
665
|
+
sni_server_certificate_from_server_socket_download_directory=(
|
|
666
|
+
self.sni_server_certificate_from_server_socket_download_directory),
|
|
667
|
+
skip_extension_id_list=self.skip_extension_id_list,
|
|
668
|
+
ca_certificate_name=self.ca_certificate_name,
|
|
669
|
+
ca_certificate_filepath=self.ca_certificate_filepath,
|
|
670
|
+
custom_server_certificate_usage=self.custom_server_certificate_usage,
|
|
671
|
+
custom_server_certificate_path=self.custom_server_certificate_path,
|
|
672
|
+
custom_private_key_path=self.custom_private_key_path,
|
|
673
|
+
domain_from_dns_server=domain_from_engine,
|
|
674
|
+
forwarding_dns_service_ipv4_list___only_for_localhost=(
|
|
675
|
+
self.forwarding_dns_service_ipv4_list___only_for_localhost),
|
|
676
|
+
tls=is_tls,
|
|
677
|
+
exceptions_logger=self.exceptions_logger,
|
|
678
|
+
enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
679
|
+
sslkeylog_file_path=self.sslkeylog_file_path
|
|
680
|
+
)
|
|
681
|
+
|
|
164
682
|
ssl_client_socket, accept_error_message = \
|
|
165
|
-
|
|
166
|
-
client_socket,
|
|
167
|
-
print_kwargs={'logger': self.logger}
|
|
683
|
+
sni_handler.wrap_socket_with_ssl_context_server_sni_extended(
|
|
684
|
+
client_socket,
|
|
685
|
+
print_kwargs={'logger': self.logger}
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
if ssl_client_socket:
|
|
689
|
+
# Handshake is done at this point, so version/cipher are available
|
|
690
|
+
self.logger.info(
|
|
691
|
+
f"TLS version={ssl_client_socket.version()} cipher={ssl_client_socket.cipher()}"
|
|
692
|
+
)
|
|
168
693
|
|
|
169
694
|
if accept_error_message:
|
|
170
695
|
# Write statistics after wrap is there was an error.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
696
|
+
self.statistics_writer.write_accept_error(
|
|
697
|
+
engine=engine_name,
|
|
698
|
+
source_host=source_hostname,
|
|
699
|
+
source_ip=source_ip,
|
|
700
|
+
error_message=accept_error_message,
|
|
701
|
+
dest_port=str(dest_port),
|
|
702
|
+
host=domain_from_engine,
|
|
703
|
+
process_name=process_name)
|
|
175
704
|
|
|
176
705
|
continue
|
|
177
706
|
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
707
|
+
# Get the real tls version after connection is wrapped.
|
|
708
|
+
tls_version = ssl_client_socket.version()
|
|
709
|
+
|
|
710
|
+
# If the 'domain_from_dns_server' is empty, it means that the 'engine_name' is not set.
|
|
711
|
+
# In this case we will set the 'engine_name' to from the SNI.
|
|
712
|
+
if engine_name == '':
|
|
713
|
+
sni_hostname: str = ssl_client_socket.server_hostname
|
|
714
|
+
if sni_hostname:
|
|
715
|
+
engine_name = get_engine_name(sni_hostname, [self.engine])
|
|
185
716
|
|
|
186
717
|
# Create new arguments tuple that will be passed, since client socket and process_name
|
|
187
718
|
# are gathered from SocketWrapper.
|
|
@@ -189,26 +720,96 @@ class SocketWrapper:
|
|
|
189
720
|
# In order to use the same object, it needs to get nullified first, since the old instance
|
|
190
721
|
# will not get overwritten. Though it still will show in the memory as SSLSocket, it will not
|
|
191
722
|
# be handled as such, but as regular raw socket.
|
|
723
|
+
# noinspection PyUnusedLocal
|
|
192
724
|
client_socket = None
|
|
193
725
|
client_socket = ssl_client_socket
|
|
194
|
-
thread_args =
|
|
195
|
-
(client_socket, process_name, is_tls,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
726
|
+
thread_args = (
|
|
727
|
+
(client_socket, process_name, is_tls, tls_type, tls_version, domain_from_engine, self.statistics_writer, [self.engine]) +
|
|
728
|
+
callable_args)
|
|
729
|
+
|
|
730
|
+
# Creating thread for each socket
|
|
731
|
+
thread_current = threading.Thread(
|
|
732
|
+
target=before_socket_thread_worker,
|
|
733
|
+
args=(callable_function, thread_args, self.exceptions_logger),
|
|
734
|
+
daemon=True
|
|
735
|
+
)
|
|
736
|
+
thread_current.start()
|
|
737
|
+
# Append to list of threads, so they can be "joined" later
|
|
738
|
+
self.threads_list.append(thread_current)
|
|
739
|
+
|
|
740
|
+
# 'thread_callable_args[1][0]' is the client socket.
|
|
741
|
+
client_address = socket_base.get_source_address_from_socket(client_socket)
|
|
742
|
+
|
|
743
|
+
self.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
|
|
204
744
|
# Else, if no client_socket was opened during, accept, then print the error.
|
|
205
745
|
else:
|
|
206
746
|
# Write statistics after accept.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
747
|
+
self.statistics_writer.write_accept_error(
|
|
748
|
+
engine=engine_name,
|
|
749
|
+
source_host=source_hostname,
|
|
750
|
+
source_ip=source_ip,
|
|
751
|
+
error_message=accept_error_message,
|
|
752
|
+
dest_port=str(dest_port),
|
|
753
|
+
host=domain_from_engine,
|
|
754
|
+
process_name=process_name)
|
|
755
|
+
# Sometimes paramiko SSH connection return EOFError on connection reset, so we need to catch it separately.
|
|
756
|
+
except (ConnectionResetError, paramiko.ssh_exception.SSHException, EOFError) as e:
|
|
757
|
+
exception_string: str = tracebacks.get_as_string()
|
|
758
|
+
full_string: str = f"{str(e)} | {exception_string}"
|
|
759
|
+
self.statistics_writer.write_accept_error(
|
|
760
|
+
engine=engine_name,
|
|
761
|
+
source_host=source_hostname,
|
|
762
|
+
source_ip=source_ip,
|
|
763
|
+
error_message=full_string,
|
|
764
|
+
dest_port=str(dest_port),
|
|
765
|
+
host=domain_from_engine,
|
|
766
|
+
process_name=process_name)
|
|
767
|
+
except Exception as e:
|
|
768
|
+
_ = e
|
|
769
|
+
exception_string: str = tracebacks.get_as_string()
|
|
770
|
+
full_string: str = f"Engine: [{engine_name}] | {exception_string}"
|
|
771
|
+
self.exceptions_logger.write(full_string)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def before_socket_thread_worker(
|
|
775
|
+
callable_function: Callable[..., Any],
|
|
776
|
+
callable_args: tuple,
|
|
777
|
+
exceptions_logger: loggingw.ExceptionCsvLogger = None
|
|
778
|
+
):
|
|
779
|
+
"""
|
|
780
|
+
Function that will be executed before the thread is started.
|
|
781
|
+
:param callable_function: callable, function that will be executed in the thread.
|
|
782
|
+
:param callable_args: tuple, arguments that will be passed to the function.
|
|
783
|
+
:param exceptions_logger: loggingw.ExceptionCsvLogger, logger object that will be used to log exceptions.
|
|
784
|
+
:return:
|
|
785
|
+
"""
|
|
786
|
+
|
|
787
|
+
try:
|
|
788
|
+
callable_function(*callable_args)
|
|
789
|
+
except Exception as e:
|
|
790
|
+
exceptions_logger.write(e, custom_exception_attribute='engine_name', custom_exception_attribute_placement='before')
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def get_engine_name(domain: str, engine_list: list):
|
|
794
|
+
"""
|
|
795
|
+
Function that will get the engine name from the domain name.
|
|
796
|
+
:param domain: string, domain name.
|
|
797
|
+
:param engine_list: list that contains the engine names and domains.
|
|
798
|
+
:return: string, engine name.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
engine_name: str = ''
|
|
802
|
+
for engine in engine_list:
|
|
803
|
+
# Get engine name by domain.
|
|
804
|
+
if domain in engine.domain_target_dict:
|
|
805
|
+
engine_name = engine.engine_name
|
|
806
|
+
|
|
807
|
+
# If didn't find by domain, try to find by port.
|
|
808
|
+
if engine_name == '':
|
|
809
|
+
for port, ip_port_to_connect_value in engine.on_port_connect.items():
|
|
810
|
+
ipv4_to_connect, _ = initialize_engines.get_ipv4_from_engine_on_connect_port(ip_port_to_connect_value)
|
|
811
|
+
if ipv4_to_connect == domain:
|
|
812
|
+
engine_name = engine.engine_name
|
|
813
|
+
break
|
|
814
|
+
|
|
815
|
+
return engine_name
|