atomicshop 3.3.8__py3-none-any.whl → 3.10.0__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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (120) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  3. atomicshop/a_mains/install_ca_certificate.py +172 -0
  4. atomicshop/a_mains/process_from_port.py +119 -0
  5. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  6. atomicshop/basics/strings.py +1 -1
  7. atomicshop/certificates.py +2 -2
  8. atomicshop/dns.py +26 -28
  9. atomicshop/etws/traces/trace_tcp.py +1 -2
  10. atomicshop/mitm/centered_settings.py +133 -0
  11. atomicshop/mitm/config_static.py +22 -44
  12. atomicshop/mitm/connection_thread_worker.py +383 -165
  13. atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
  14. atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
  15. atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
  16. atomicshop/mitm/engines/create_module_template.py +1 -2
  17. atomicshop/mitm/import_config.py +91 -89
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +238 -122
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/mitm/statistic_analyzer.py +33 -12
  24. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
  25. atomicshop/networks.py +160 -92
  26. atomicshop/package_mains_processor.py +84 -0
  27. atomicshop/permissions/ubuntu_permissions.py +47 -0
  28. atomicshop/print_api.py +3 -5
  29. atomicshop/process.py +11 -4
  30. atomicshop/python_functions.py +23 -108
  31. atomicshop/speech_recognize.py +8 -0
  32. atomicshop/ssh_remote.py +140 -164
  33. atomicshop/web.py +63 -22
  34. atomicshop/web_apis/google_llm.py +22 -14
  35. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  36. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  37. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  38. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  39. atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
  40. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  41. atomicshop/wrappers/githubw.py +180 -68
  42. atomicshop/wrappers/loggingw/consts.py +1 -1
  43. atomicshop/wrappers/loggingw/handlers.py +1 -1
  44. atomicshop/wrappers/loggingw/loggingw.py +20 -4
  45. atomicshop/wrappers/loggingw/reading.py +18 -0
  46. atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
  47. atomicshop/wrappers/netshw.py +124 -3
  48. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  49. atomicshop/wrappers/powershell_networking.py +80 -0
  50. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  51. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  52. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  53. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  54. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  55. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  56. atomicshop/wrappers/socketw/certificator.py +19 -9
  57. atomicshop/wrappers/socketw/creator.py +101 -14
  58. atomicshop/wrappers/socketw/dns_server.py +17 -5
  59. atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
  60. atomicshop/wrappers/socketw/process_getter.py +86 -0
  61. atomicshop/wrappers/socketw/receiver.py +29 -9
  62. atomicshop/wrappers/socketw/sender.py +10 -9
  63. atomicshop/wrappers/socketw/sni.py +31 -10
  64. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  65. atomicshop/wrappers/socketw/socket_client.py +11 -10
  66. atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
  67. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  68. atomicshop/wrappers/ubuntu_terminal.py +21 -18
  69. atomicshop/wrappers/win_auditw.py +189 -0
  70. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  71. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
  72. atomicshop/_basics_temp.py +0 -101
  73. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  74. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  75. atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
  76. atomicshop/a_installs/ubuntu/mongodb.py +0 -12
  77. atomicshop/a_installs/win/fibratus.py +0 -9
  78. atomicshop/a_installs/win/mongodb.py +0 -9
  79. atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
  80. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  81. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  82. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  83. atomicshop/addons/package_setup/Setup.cmd +0 -7
  84. atomicshop/archiver/__init__.py +0 -0
  85. atomicshop/archiver/_search_in_zip.py +0 -189
  86. atomicshop/archiver/search_in_archive.py +0 -284
  87. atomicshop/archiver/sevenz_app_w.py +0 -86
  88. atomicshop/archiver/sevenzs.py +0 -73
  89. atomicshop/archiver/shutils.py +0 -34
  90. atomicshop/archiver/zips.py +0 -353
  91. atomicshop/file_types.py +0 -24
  92. atomicshop/pbtkmultifile_argparse.py +0 -88
  93. atomicshop/script_as_string_processor.py +0 -42
  94. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  95. atomicshop/ssh_scripts/process_from_port.py +0 -27
  96. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  97. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  98. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  99. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
  100. atomicshop/wrappers/ffmpegw.py +0 -125
  101. atomicshop/wrappers/fibratusw/__init__.py +0 -0
  102. atomicshop/wrappers/fibratusw/install.py +0 -80
  103. atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
  104. atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
  105. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  106. atomicshop/wrappers/socketw/get_process.py +0 -123
  107. atomicshop/wrappers/wslw.py +0 -192
  108. atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
  109. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  110. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  111. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  112. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  113. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  114. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  115. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  116. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  117. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  118. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  119. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  120. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import socket
2
+ import time
2
3
 
3
4
 
4
5
  LOCALHOST_IPV4: str = '127.0.0.1'
@@ -99,4 +100,35 @@ def get_host_name_from_ip_address(ip_address: str) -> str:
99
100
  host_name, alias_list, ipaddr_list = socket.gethostbyaddr(ip_address)
100
101
  _ = alias_list, ipaddr_list
101
102
 
102
- return host_name
103
+ return host_name
104
+
105
+
106
+ def wait_for_ip_bindable(
107
+ ip: str,
108
+ port: int = 0,
109
+ timeout: float = 15.0,
110
+ interval: float = 0.5,
111
+ ) -> None:
112
+ """
113
+ Wait until a single IP is bindable (or timeout).
114
+
115
+ Raises TimeoutError if the IP cannot be bound within 'timeout' seconds.
116
+ """
117
+ deadline = time.time() + timeout
118
+ last_err: OSError | None = None
119
+
120
+ while time.time() < deadline:
121
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
122
+ try:
123
+ s.bind((ip, port))
124
+ s.close()
125
+ return # success
126
+ except OSError as e:
127
+ last_err = e
128
+ s.close()
129
+ time.sleep(interval)
130
+
131
+ raise TimeoutError(
132
+ f"IP {ip} not bindable within {timeout} seconds; "
133
+ f"last error: {last_err}"
134
+ )
@@ -31,7 +31,8 @@ class SocketClient:
31
31
  dns_servers_list: list[str] = None,
32
32
  logger: logging.Logger = None,
33
33
  custom_pem_client_certificate_file_path: str = None,
34
- enable_sslkeylogfile_env_to_client_ssl_context: bool = False
34
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
35
+ sslkeylog_file_path:str = None
35
36
  ):
36
37
  """
37
38
  If you have a certificate for domain, but not for the IPv4 address, the SSL Socket context can be created for
@@ -68,6 +69,7 @@ class SocketClient:
68
69
  self.dns_servers_list = dns_servers_list
69
70
  self.custom_pem_client_certificate_file_path: str = custom_pem_client_certificate_file_path
70
71
  self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
72
+ self.sslkeylog_file_path: str = sslkeylog_file_path
71
73
 
72
74
  if logger:
73
75
  # Create child logger for the provided logger with the module's name.
@@ -101,7 +103,8 @@ class SocketClient:
101
103
  socket_object = creator.create_socket_ipv4_tcp()
102
104
  return creator.wrap_socket_with_ssl_context_client___default_certs___ignore_verification(
103
105
  socket_object, self.service_name, self.custom_pem_client_certificate_file_path,
104
- enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context
106
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
107
+ sslkeylog_file_path=self.sslkeylog_file_path
105
108
  )
106
109
 
107
110
  def service_connection(
@@ -215,7 +218,6 @@ class SocketClient:
215
218
  self.socket_instance = None
216
219
  self.logger.info(f"Closed socket to service server [{self.service_name}:{self.service_port}]")
217
220
 
218
- # noinspection PyUnusedLocal
219
221
  def send_receive_to_service(
220
222
  self,
221
223
  request_bytes: Union[bytearray, bytes],
@@ -228,9 +230,8 @@ class SocketClient:
228
230
  :param skip_send: If True, the data will not be sent to the service server. After the connection is established,
229
231
  the function will wait for the response only.
230
232
  """
231
- # Define variables
232
- function_service_data = None
233
- error_message = None
233
+
234
+ origin_data: bytes | None = None
234
235
 
235
236
  service_socket, error_message = self.service_connection()
236
237
  # If connection to service server wasn't successful
@@ -254,7 +255,7 @@ class SocketClient:
254
255
  if not skip_send:
255
256
  # Send the data received from the client to the service over socket
256
257
  error_on_send = Sender(
257
- ssl_socket=self.socket_instance, class_message=request_bytes, logger=self.logger).send()
258
+ ssl_socket=self.socket_instance, bytes_to_send=request_bytes, logger=self.logger).send()
258
259
 
259
260
  # If the socket disconnected on data send
260
261
  if error_on_send:
@@ -265,17 +266,17 @@ class SocketClient:
265
266
 
266
267
  # Else if send was successful
267
268
  if not error_on_send:
268
- function_service_data = Receiver(
269
+ origin_data, is_socket_closed, error_message = Receiver(
269
270
  ssl_socket=self.socket_instance, logger=self.logger).receive()
270
271
 
271
272
  # If data received is empty meaning the socket was closed on the other side
272
- if not function_service_data:
273
+ if not origin_data:
273
274
  error_message = "Service server closed the connection on receive"
274
275
 
275
276
  # We'll close the socket and nullify the object
276
277
  self.close_socket()
277
278
 
278
- return function_service_data, error_message, self.connection_ip, self.socket_instance
279
+ return origin_data, error_message, self.connection_ip, self.socket_instance
279
280
 
280
281
  def send_receive_message_list_with_interval(
281
282
  self, requests_bytes_list: list, intervals_list: list, intervals_defaults: int, cycles: int = 1):
@@ -7,17 +7,20 @@ import socket
7
7
  import shutil
8
8
  import os
9
9
 
10
+ import paramiko
11
+
10
12
  from ...mitm import initialize_engines
11
13
  from ..psutilw import psutil_networks
12
14
  from ..certauthw import certauthw
13
15
  from ..loggingw import loggingw
14
- from ...script_as_string_processor import ScriptAsStringProcessor
16
+ from ... import package_mains_processor
15
17
  from ...permissions import permissions
16
18
  from ... import filesystem, certificates
17
- from ...basics import booleans
19
+ from ...basics import booleans, tracebacks
18
20
  from ...print_api import print_api
21
+ from ...ssh_remote import SSHRemote
19
22
 
20
- from . import base, creator, get_process, accepter, statistics_csv, ssl_base, sni
23
+ from . import socket_base, creator, process_getter, accepter, statistics_csv, ssl_base, sni
21
24
 
22
25
 
23
26
  class SocketWrapperPortInUseError(Exception):
@@ -65,10 +68,7 @@ class SocketWrapper:
65
68
  ssh_user: str = None,
66
69
  ssh_pass: str = None,
67
70
  ssh_script_to_execute: Union[
68
- Literal[
69
- 'process_from_port',
70
- 'process_from_ipv4'
71
- ],
71
+ Literal['process_from_port'],
72
72
  None
73
73
  ] = None,
74
74
  logs_directory: str = None,
@@ -78,6 +78,8 @@ class SocketWrapper:
78
78
  statistics_logger_queue: multiprocessing.Queue = None,
79
79
  exceptions_logger_name: str = 'SocketWrapperExceptions',
80
80
  exceptions_logger_queue: multiprocessing.Queue = None,
81
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
82
+ sslkeylog_file_path: str = None,
81
83
  print_kwargs: dict = None,
82
84
  ):
83
85
  """
@@ -171,6 +173,12 @@ class SocketWrapper:
171
173
  :param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
172
174
  :param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
173
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.
174
182
  :param print_kwargs: dict, additional arguments to pass to the print function.
175
183
  """
176
184
 
@@ -206,6 +214,9 @@ class SocketWrapper:
206
214
  self.ssh_script_to_execute = ssh_script_to_execute
207
215
  self.forwarding_dns_service_ipv4_list___only_for_localhost = (
208
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
209
220
  self.print_kwargs: dict = print_kwargs
210
221
 
211
222
  self.socket_object = None
@@ -224,12 +235,17 @@ class SocketWrapper:
224
235
  # Defining listening sockets list, which will be used with "select" library in 'loop_for_incoming_sockets'.
225
236
  self.listening_sockets: list = list()
226
237
 
227
- # 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.
228
239
  self.ssh_script_processor = None
229
240
  if self.get_process_name:
230
241
  # noinspection PyTypeChecker
231
- self.ssh_script_processor = \
232
- ScriptAsStringProcessor().read_script_to_string(self.ssh_script_to_execute)
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
233
249
 
234
250
  # If logs directory was not set, we will use the working directory.
235
251
  if not logs_directory:
@@ -378,7 +394,8 @@ class SocketWrapper:
378
394
 
379
395
  os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
380
396
  print_api("Removed cached server certificates.", logger=self.logger)
381
-
397
+ else:
398
+ os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
382
399
 
383
400
  if self.install_ca_certificate_to_root_store:
384
401
  if not self.ca_certificate_filepath:
@@ -497,6 +514,13 @@ class SocketWrapper:
497
514
  listening_sockets: list = [listening_socket_object]
498
515
 
499
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 = ''
523
+
500
524
  try:
501
525
  # Using "select.select" which is currently the only API function that works on all
502
526
  # operating system types: Windows / Linux / BSD.
@@ -507,7 +531,6 @@ class SocketWrapper:
507
531
 
508
532
  listening_ip, listening_port = listening_socket_object.getsockname()
509
533
 
510
- domain_from_engine = None
511
534
  # Get the domain to connect on this process in case on no SNI provided.
512
535
  for domain, ip_port_dict in self.engine.domain_target_dict.items():
513
536
  if ip_port_dict['ip'] == listening_ip:
@@ -533,41 +556,88 @@ class SocketWrapper:
533
556
 
534
557
  self.logger.info(f"Requested domain setting: {domain_from_engine}")
535
558
 
559
+ engine_name = get_engine_name(domain_from_engine, [self.engine])
560
+
536
561
  # Wait from any connection on "accept()".
537
562
  # 'client_socket' is socket or ssl socket, 'client_address' is a tuple (ip_address, port).
538
563
  client_socket, client_address, accept_error_message = accepter.accept_connection_with_error(
539
564
  listening_socket_object, domain_from_dns_server=domain_from_engine,
540
565
  print_kwargs={'logger': self.logger})
541
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
580
+
542
581
  # This is the earliest stage to ask for process name.
543
582
  # SSH Remote / LOCALHOST script execution to identify process section.
544
583
  # If 'get_process_name' was set to True, then this will be executed.
545
- process_name = None
546
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
+
547
590
  # Get the process name from the socket.
548
- get_command_instance = get_process.GetCommandLine(
549
- client_socket=client_socket,
550
- ssh_script_processor=self.ssh_script_processor,
551
- ssh_user=self.ssh_user,
552
- ssh_pass=self.ssh_pass,
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,
553
596
  logger=self.logger)
554
597
  process_name = get_command_instance.get_process_name(print_kwargs={'logger': self.logger})
555
598
 
556
- source_ip: str = client_address[0]
557
- engine_name: str = get_engine_name(domain_from_engine, [self.engine])
558
- dest_port: int = listening_socket_object.getsockname()[1]
559
-
560
- # Not always there will be a hostname resolved by the IP address, so we will leave it empty if it fails.
561
- try:
562
- source_hostname: str = socket.gethostbyaddr(source_ip)[0]
563
- except socket.herror:
564
- source_hostname = ''
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.")
565
617
 
566
618
  # If 'accept()' function worked well, SSL worked well, then 'client_socket' won't be empty.
567
619
  if client_socket:
568
620
  # Get the protocol type from the socket.
569
621
  is_tls: bool = False
570
- 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
+
571
641
  if tls_properties:
572
642
  is_tls = True
573
643
  tls_type, tls_version = tls_properties
@@ -604,7 +674,9 @@ class SocketWrapper:
604
674
  forwarding_dns_service_ipv4_list___only_for_localhost=(
605
675
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
606
676
  tls=is_tls,
607
- exceptions_logger=self.exceptions_logger
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
608
680
  )
609
681
 
610
682
  ssl_client_socket, accept_error_message = \
@@ -613,6 +685,12 @@ class SocketWrapper:
613
685
  print_kwargs={'logger': self.logger}
614
686
  )
615
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
+ )
693
+
616
694
  if accept_error_message:
617
695
  # Write statistics after wrap is there was an error.
618
696
  self.statistics_writer.write_accept_error(
@@ -660,7 +738,7 @@ class SocketWrapper:
660
738
  self.threads_list.append(thread_current)
661
739
 
662
740
  # 'thread_callable_args[1][0]' is the client socket.
663
- client_address = base.get_source_address_from_socket(client_socket)
741
+ client_address = socket_base.get_source_address_from_socket(client_socket)
664
742
 
665
743
  self.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
666
744
  # Else, if no client_socket was opened during, accept, then print the error.
@@ -674,8 +752,23 @@ class SocketWrapper:
674
752
  dest_port=str(dest_port),
675
753
  host=domain_from_engine,
676
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)
677
767
  except Exception as e:
678
- self.exceptions_logger.write(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)
679
772
 
680
773
 
681
774
  def before_socket_thread_worker(
@@ -694,7 +787,7 @@ def before_socket_thread_worker(
694
787
  try:
695
788
  callable_function(*callable_args)
696
789
  except Exception as e:
697
- exceptions_logger.write(e)
790
+ exceptions_logger.write(e, custom_exception_attribute='engine_name', custom_exception_attribute_placement='before')
698
791
 
699
792
 
700
793
  def get_engine_name(domain: str, engine_list: list):
@@ -24,15 +24,19 @@ def convert_der_x509_bytes_to_pem_string(certificate) -> str:
24
24
  return ssl.DER_cert_to_PEM_cert(certificate)
25
25
 
26
26
 
27
- def is_tls(client_socket) -> Optional[Tuple[str, str]]:
27
+ def is_tls(
28
+ client_socket,
29
+ timeout: float = None
30
+ ) -> Optional[Tuple[str, str]]:
28
31
  """
29
32
  Return protocol type of the incoming socket after 'accept()'.
30
33
  :param client_socket: incoming socket after 'accept()'.
34
+ :param timeout: optional timeout for receiving/peeking the first bytes.
31
35
  :return: tuple with content type, protocol type + version.
32
36
  If the length of the first bytes is less than 3, return None.
33
37
  """
34
38
 
35
- first_bytes = receiver.peek_first_bytes(client_socket, bytes_amount=3)
39
+ first_bytes = receiver.peek_first_bytes(client_socket, bytes_amount=3, timeout=timeout)
36
40
 
37
41
  # Sometimes only one byte is available, so we need to handle that case.
38
42
  # convert to a tuple of ints, add three Nones, and keep only the first 3 items.
@@ -4,10 +4,15 @@ import subprocess
4
4
  import shutil
5
5
  import time
6
6
 
7
+ from rich.console import Console
8
+
7
9
  from ..print_api import print_api
8
10
  from ..permissions import ubuntu_permissions
9
11
 
10
12
 
13
+ console = Console()
14
+
15
+
11
16
  def install_packages(
12
17
  package_list: list[str],
13
18
  timeout_seconds: int = 0,
@@ -110,7 +115,7 @@ def update_system_packages():
110
115
  Function updates the system packages.
111
116
  :return:
112
117
  """
113
- subprocess.check_call(['sudo', 'apt-get', 'update'])
118
+ subprocess.check_call(['sudo', 'apt', 'update'])
114
119
 
115
120
 
116
121
  def upgrade_system_packages(apt_update: bool = True):
@@ -124,7 +129,7 @@ def upgrade_system_packages(apt_update: bool = True):
124
129
  if apt_update:
125
130
  update_system_packages()
126
131
 
127
- subprocess.check_call(['sudo', 'apt-get', 'upgrade', '-y'])
132
+ subprocess.check_call(['sudo', 'apt', 'upgrade', '-y'])
128
133
 
129
134
 
130
135
  def is_service_running(service_name: str, user_mode: bool = False, return_false_on_error: bool = False) -> bool:
@@ -211,28 +216,25 @@ def start_service(service_name: str, sudo: bool = False, user_mode: bool = False
211
216
  def start_enable_service_check_availability(
212
217
  service_name: str,
213
218
  wait_time_seconds: float = 30,
214
- exit_on_error: bool = True,
215
219
  start_service_bool: bool = True,
216
220
  enable_service_bool: bool = True,
217
221
  check_service_running: bool = True,
218
222
  user_mode: bool = False,
219
- sudo: bool = True,
220
- print_kwargs: dict = None
221
- ):
223
+ sudo: bool = True
224
+ ) -> int:
222
225
  """
223
226
  Function starts and enables a service and checks its availability.
224
227
 
225
228
  :param service_name: str, the service name.
226
229
  :param wait_time_seconds: float, the time to wait after starting the service before checking the service
227
230
  availability.
228
- :param exit_on_error: bool, if True, the function will exit the program if the service is not available.
229
231
  :param start_service_bool: bool, if True, the service will be started.
230
232
  :param enable_service_bool: bool, if True, the service will be enabled.
231
233
  :param check_service_running: bool, if True, the function will check if the service is running.
232
234
  :param user_mode: bool, if True, the service will be started and enabled in user mode.
233
235
  :param sudo: bool, if True, the command will be executed with sudo.
234
- :param print_kwargs: dict, the print arguments.
235
- :return:
236
+
237
+ :return: int, 0 if the service is running, 1 if the service is not running.
236
238
  """
237
239
 
238
240
  if not start_service_bool and not enable_service_bool:
@@ -245,18 +247,19 @@ def start_enable_service_check_availability(
245
247
  enable_service(service_name, user_mode=user_mode,sudo=sudo)
246
248
 
247
249
  if check_service_running:
248
- print_api(
249
- f"Waiting {str(wait_time_seconds)} seconds for the program to start before availability check...",
250
- **(print_kwargs or {}))
251
- time.sleep(wait_time_seconds)
250
+ print(f"Waiting up to {str(wait_time_seconds)} seconds for the program to start.")
251
+ count: int = 0
252
+ while not is_service_running(service_name, user_mode=user_mode) and count < wait_time_seconds:
253
+ count += 1
254
+ time.sleep(1)
252
255
 
253
256
  if not is_service_running(service_name, user_mode=user_mode):
254
- print_api(
255
- f"[{service_name}] service failed to start.", color='red', error_type=True, **(print_kwargs or {}))
256
- if exit_on_error:
257
- sys.exit(1)
257
+ console.print(f"[{service_name}] service failed to start.", style='red', markup=False)
258
+ return 1
258
259
  else:
259
- print_api(f"[{service_name}] service is running.", color='green', **(print_kwargs or {}))
260
+ console.print(f"[{service_name}] service is running.", style='green', markup=False)
261
+
262
+ return 0
260
263
 
261
264
 
262
265
  def add_path_to_bashrc(as_regular_user: bool = False):