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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {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 . import base, creator, get_process, accepter, statistics_csv, ssl_base
5
- from ...script_as_string_processor import ScriptAsStringProcessor
6
- from ... import queues
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
- SNI_QUEUE = queues.NonBlockQueue()
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
- logger=None,
18
- statistics_logger=None,
19
- config=None,
20
- domains_list: list = None
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.logger = logger
24
- self.statistics = statistics_logger
25
- self.config: dict = config
26
-
27
- # If 'domains_list' wasn't passed, but 'config' did.
28
- if not domains_list and config:
29
- self.domains_list: list = config['certificates']['domains_all_times']
30
- else:
31
- self.domains_list: list = domains_list
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 'ssh_script_processor' variable, which will be used to process SSH scripts.
238
+ # Defining 'ssh_script_string' variable, which will be used to process SSH scripts.
51
239
  self.ssh_script_processor = None
52
- if self.config['ssh']['get_process_name']:
53
- self.ssh_script_processor = \
54
- ScriptAsStringProcessor().read_script_to_string(self.config['ssh']['script_to_execute'])
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 create_tcp_listening_socket_list(self, overwrite_list: bool = False):
70
- # If 'overwrite_list' was set to 'True', we will create new list. The default is 'False', since it is meant to`
71
- # add new sockets to already existing ones.
72
- if overwrite_list:
73
- self.listening_sockets = list()
74
-
75
- # Creating a socket for each port in the list set in configuration file
76
- for port in self.config['tcp']['listening_port_list']:
77
- socket_by_port = self.create_socket_ipv4_tcp(
78
- self.config['tcp']['listening_interface'], port)
79
-
80
- self.listening_sockets.append(socket_by_port)
81
-
82
- def send_accepted_socket_to_thread(self, thread_function_name, reference_args=()):
83
- # Creating thread for each socket
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
- # 'reference_args[0]' is the client socket.
91
- client_address = base.get_source_address_from_socket(reference_args[0])
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.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
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 loop_for_incoming_sockets(
96
- self, function_reference, listening_socket_list: list = None,
97
- pass_function_reference_to_thread: bool = True, reference_args=(), *args, **kwargs):
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 function_reference: callable, function reference that you want to execute when client
104
- socket received by 'accept()' and connection been made.
105
- :param listening_socket_list: list, of sockets that you want to listen on.
106
- :param pass_function_reference_to_thread: boolean, that sets if 'function_reference' will be
107
- executed as is, or passed to thread. 'function_reference' can include passing to a thread,
108
- but you don't have to use it, since SocketWrapper can do it for you.
109
- :param reference_args: tuple, that will be passed to 'function_reference' when it will be called.
110
- :param kwargs:
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
- # If 'listening_socket_list' wasn't specified and 'self.listening_sockets' is not empty.
115
- if not listening_socket_list and self.listening_sockets:
116
- # Then assign 'self.listening_sockets'.
117
- listening_socket_list = self.listening_sockets
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(listening_socket_list, [], [])
529
+ readable, writable, exceptional = select.select(listening_sockets, [], [])
129
530
  listening_socket_object = readable[0]
130
531
 
131
- # Get the domain queue. Tried using "Queue.Queue" object, but it stomped the SSL Sockets
132
- # from accepting connections.
133
- domain_from_dns_server = None
134
- if self.requested_domain_from_dns_server.queue:
135
- domain_from_dns_server = self.requested_domain_from_dns_server.queue
136
- self.logger.info(f"Requested domain from DNS Server: {self.requested_domain_from_dns_server.queue}")
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, dns_domain=domain_from_dns_server, print_kwargs={'logger': self.logger})
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 'config.tcp['get_process_name']' was set to True in 'config.ini', then this will be executed.
146
- process_name = None
147
- if self.config['ssh']['get_process_name']:
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
- process_name = get_process.get_process_name(
150
- client_socket=client_socket, config=self.config, ssh_script_processor=self.ssh_script_processor,
151
- print_kwargs={'logger': self.logger})
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
- tls_properties = ssl_base.is_tls(client_socket)
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
- creator.wrap_socket_with_ssl_context_server_sni_extended(
166
- client_socket, config=self.config, dns_domain=domain_from_dns_server,
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
- statistics_csv.write_accept_error(
172
- error_message=accept_error_message, host=domain_from_dns_server,
173
- process_name=process_name,
174
- statistics_logger=self.statistics, print_kwargs={'logger': self.logger})
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
- # ready_to_read, _, _ = select.select([client_socket], [], [])
179
- # if ready_to_read:
180
- # try:
181
- # # self.socket_object.do_handshake()
182
- # self.socket_object.accept()
183
- # except Exception:
184
- # raise
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, domain_from_dns_server) + reference_args
196
- # If 'pass_function_reference_to_thread' was set to 'False', execute the callable passed function
197
- # as is.
198
- if not pass_function_reference_to_thread:
199
- function_reference(thread_args, *args, **kwargs)
200
- # If 'pass_function_reference_to_thread' was set to 'True', execute the callable function reference
201
- # in a new thread.
202
- else:
203
- self.send_accepted_socket_to_thread(function_reference, thread_args)
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
- statistics_csv.write_accept_error(
208
- error_message=accept_error_message, host=domain_from_dns_server, process_name=process_name,
209
- statistics_logger=self.statistics, print_kwargs={'logger': self.logger})
210
- except Exception:
211
- print_api("Undocumented exception in while loop of listening sockets.", error_type=True,
212
- logger_method="error", traceback_string=True, oneline=True, logger=self.logger)
213
- pass
214
- continue
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