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
@@ -2,7 +2,7 @@ import os
2
2
  import socket
3
3
  import ssl
4
4
 
5
- from . import base, exception_wrapper
5
+ from . import socket_base, exception_wrapper
6
6
  from ...print_api import print_api
7
7
 
8
8
 
@@ -25,28 +25,63 @@ def add_reusable_address_option(socket_instance):
25
25
  socket_instance.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
26
26
 
27
27
 
28
- def create_ssl_context_for_server() -> ssl.SSLContext:
28
+ def create_ssl_context_for_server(
29
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
30
+ sslkeylog_file_path: str = None,
31
+ allow_legacy: bool = False
32
+ ) -> ssl.SSLContext:
33
+ """
34
+ This function creates the SSL context for the server.
35
+ Meaning that your script will act like a server, and the client will connect to it.
36
+ """
29
37
  # Creating context with SSL certificate and the private key before the socket
30
38
  # https://docs.python.org/3/library/ssl.html
31
39
  # Creating context for SSL wrapper, specifying "PROTOCOL_TLS_SERVER" will pick the best TLS version protocol for
32
40
  # the server.
33
41
 
34
- ssl_context: ssl.SSLContext = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
42
+ # ssl_context: ssl.SSLContext = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
35
43
 
36
44
  # # Enforce the use of TLS 1.2 only (disable TLS 1.0, TLS 1.1, and TLS 1.3)
37
45
  # ssl_context.options |= ssl.OP_NO_TLSv1 # Disable TLS 1.0
38
46
  # ssl_context.options |= ssl.OP_NO_TLSv1_1 # Disable TLS 1.1
39
47
  # ssl_context.options |= ssl.OP_NO_TLSv1_3 # Disable TLS 1.3
40
48
 
41
- # return ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
49
+ # Correct factory for servers
50
+ ssl_context: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
51
+
52
+ # Modern default; relax only if you must
53
+ ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
54
+
55
+ # Don't verify client certificates.
56
+ ssl_context.verify_mode = ssl.CERT_NONE
57
+ ssl_context.check_hostname = False
58
+
59
+ if enable_sslkeylogfile_env_to_client_ssl_context:
60
+ if sslkeylog_file_path is None:
61
+ sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
62
+
63
+ if not os.path.exists(sslkeylog_file_path):
64
+ open(sslkeylog_file_path, "a").close()
65
+
66
+ ssl_context.keylog_filename = sslkeylog_file_path
67
+
68
+ # If you must support old clients that only offer TLS_RSA_* suites under OpenSSL 3:
69
+ if allow_legacy:
70
+ # This enables RSA key exchange and other legacy bits at security level 1
71
+ ssl_context.set_ciphers('DEFAULT:@SECLEVEL=1')
72
+ # If you truly have TLS 1.0/1.1 clients, uncomment the next line (not recommended):
73
+ ssl_context.minimum_version = ssl.TLSVersion.TLSv1
74
+
42
75
  return ssl_context
43
76
 
44
77
 
45
78
  def create_ssl_context_for_client(
46
- enable_sslkeylogfile_env_to_client_ssl_context: bool = False
79
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
80
+ sslkeylog_file_path: str = None
47
81
  ) -> ssl.SSLContext:
48
82
  """
49
83
  This function creates the SSL context for the client.
84
+ This means that your script will act like a client, and will connect to a server.
50
85
  The SSL context is created with the "PROTOCOL_TLS_CLIENT" protocol.
51
86
 
52
87
  :param enable_sslkeylogfile_env_to_client_ssl_context: boolean, enables the SSLKEYLOGFILE environment variable
@@ -56,15 +91,23 @@ def create_ssl_context_for_client(
56
91
  This is useful for debugging SSL/TLS connections with WireShark.
57
92
  Since WireShark also uses this environment variable to read the key log file and apply to the SSL/TLS
58
93
  connections, so you can see the decrypted traffic.
94
+ :param sslkeylog_file_path: string, full file path for the SSL key log file. Default is None.
59
95
 
60
96
  :return: ssl.SSLContext
61
97
  """
62
98
  ssl_context: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
63
99
 
64
100
  if enable_sslkeylogfile_env_to_client_ssl_context:
65
- ssl_key_logfile = os.environ.get('SSLKEYLOGFILE')
66
- if ssl_key_logfile:
67
- ssl_context.keylog_filename = ssl_key_logfile
101
+ if sslkeylog_file_path is None:
102
+ sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
103
+
104
+ if not os.path.exists(sslkeylog_file_path):
105
+ open(sslkeylog_file_path, "a").close()
106
+
107
+ ssl_context.keylog_filename = sslkeylog_file_path
108
+
109
+ current_ciphers = 'AES256-GCM-SHA384:' + ssl._DEFAULT_CIPHERS
110
+ ssl_context.set_ciphers(current_ciphers)
68
111
 
69
112
  return ssl_context
70
113
 
@@ -138,9 +181,48 @@ def load_certificate_and_key_into_server_ssl_context(
138
181
  print_api(message, error_type=True, logger_method="critical", **print_kwargs)
139
182
 
140
183
 
141
- def create_server_ssl_context___load_certificate_and_key(certificate_file_path: str, key_file_path) -> ssl.SSLContext:
184
+ def copy_server_ctx_settings(src: ssl.SSLContext, dst: ssl.SSLContext) -> None:
185
+ # Versions & options
186
+ try: dst.minimum_version = src.minimum_version
187
+ except Exception: pass
188
+ try: dst.maximum_version = src.maximum_version
189
+ except Exception: pass
190
+ try: dst.options = src.options
191
+ except Exception: pass
192
+
193
+ # Verification knobs (server usually CERT_NONE unless you do mTLS)
194
+ try: dst.verify_mode = src.verify_mode
195
+ except Exception: pass
196
+ try: dst.check_hostname = src.check_hostname
197
+ except Exception: pass
198
+
199
+ # Cipher policy – replicate current enabled list
200
+ try:
201
+ cipher_names = ':'.join(c['name'] for c in src.get_ciphers())
202
+ if cipher_names:
203
+ dst.set_ciphers(cipher_names)
204
+ except Exception:
205
+ pass
206
+
207
+ # (ALPN/curves/etc. don’t have public getters; set them the same way you set them on src, if applicable)
208
+
209
+
210
+ def create_server_ssl_context___load_certificate_and_key(
211
+ certificate_file_path: str,
212
+ key_file_path: str | None,
213
+ inherit_from: ssl.SSLContext | None = None,
214
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
215
+ sslkeylog_file_path: str = None,
216
+ ) -> ssl.SSLContext:
142
217
  # Create and set ssl context for server.
143
- ssl_context: ssl.SSLContext = create_ssl_context_for_server()
218
+ ssl_context: ssl.SSLContext = create_ssl_context_for_server(
219
+ allow_legacy=True, enable_sslkeylogfile_env_to_client_ssl_context=enable_sslkeylogfile_env_to_client_ssl_context,
220
+ sslkeylog_file_path=sslkeylog_file_path)
221
+
222
+ # If you replaced contexts during SNI, copy policy from the old one
223
+ if inherit_from is not None:
224
+ copy_server_ctx_settings(inherit_from, ssl_context)
225
+
144
226
  # Load certificate into context.
145
227
  load_certificate_and_key_into_server_ssl_context(ssl_context, certificate_file_path, key_file_path)
146
228
  # Return ssl context only.
@@ -231,7 +313,7 @@ def set_listen_on_socket(socket_object, **kwargs):
231
313
  # To determine the maximum listening sockets, you may use the 'socket' library and 'SOMAXCONN' parameter
232
314
  # from it.
233
315
  socket_object.listen(socket.SOMAXCONN)
234
- ip_address, port = base.get_destination_address_from_socket(socket_object)
316
+ ip_address, port = socket_base.get_destination_address_from_socket(socket_object)
235
317
 
236
318
  print_api(f"Listening for new connections on: {ip_address}:{port}", **kwargs)
237
319
 
@@ -243,8 +325,9 @@ def wrap_socket_with_ssl_context_client___default_certs___ignore_verification(
243
325
  socket_object,
244
326
  server_hostname: str = None,
245
327
  custom_pem_client_certificate_file_path: str = None,
246
- enable_sslkeylogfile_env_to_client_ssl_context: bool = False
247
- ):
328
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
329
+ sslkeylog_file_path: str = None
330
+ ) -> ssl.SSLSocket:
248
331
  """
249
332
  This function is a preset for wrapping the socket with SSL context for the client.
250
333
  It sets the CA default certificates, and ignores the server's certificate verification.
@@ -255,9 +338,13 @@ def wrap_socket_with_ssl_context_client___default_certs___ignore_verification(
255
338
  Default is None.
256
339
  :param enable_sslkeylogfile_env_to_client_ssl_context: boolean, enables the SSLKEYLOGFILE environment variable
257
340
  to the SSL context. Default is False.
341
+ :param sslkeylog_file_path: string, full file path for the SSL key log file. Default is None.
342
+
343
+ :return: ssl.SSLSocket object
258
344
  """
259
345
  ssl_context: ssl.SSLContext = create_ssl_context_for_client(
260
- enable_sslkeylogfile_env_to_client_ssl_context=enable_sslkeylogfile_env_to_client_ssl_context)
346
+ enable_sslkeylogfile_env_to_client_ssl_context=enable_sslkeylogfile_env_to_client_ssl_context
347
+ ,sslkeylog_file_path=sslkeylog_file_path)
261
348
  set_client_ssl_context_ca_default_certs(ssl_context)
262
349
  set_client_ssl_context_certificate_verification_ignore(ssl_context)
263
350
 
@@ -13,6 +13,7 @@ from ..loggingw import loggingw
13
13
  from ..psutilw import psutil_networks
14
14
  from ...basics import booleans, tracebacks
15
15
  from ...file_io import csvs
16
+ from ... import networks
16
17
 
17
18
  # noinspection PyPackageRequirements
18
19
  import dnslib
@@ -302,6 +303,17 @@ class DnsServer:
302
303
  time.sleep(1)
303
304
  raise DnsConfigurationValuesError(e)
304
305
 
306
+ # If the listening interface is not localhost, check if the interface can be bound to.
307
+ if not self.listening_interface.startswith('127.'):
308
+ host_ips: list[str] = networks.get_host_ips_psutil(ipv6=False)
309
+ if self.listening_interface not in host_ips:
310
+ message = (f"Listening interface [{self.listening_interface}] is not assigned to any of the host "
311
+ f"network interfaces. Current host IPv4 addresses: {host_ips}")
312
+ print_api(f'DnsConfigurationValuesError: {str(message)}', error_type=True, color="red", logger=self.logger)
313
+ # Wait for the message to be printed and saved to file.
314
+ time.sleep(1)
315
+ raise DnsConfigurationValuesError(message)
316
+
305
317
  ips_ports: list[str] = [f'{self.listening_interface}:{self.listening_port}']
306
318
  port_in_use = psutil_networks.get_processes_using_port_list(ips_ports)
307
319
  if port_in_use:
@@ -466,7 +478,7 @@ class DnsServer:
466
478
  dns_cached_request = False
467
479
  # Check if the received data request from client is already in the cache
468
480
  if client_data in self.dns_questions_to_answers_cache:
469
- # message = "!!! Question / Answer is already in the dictionary..."
481
+ # message = "!!! Request / Response is already in the dictionary..."
470
482
  # self.logger.info(message)
471
483
 
472
484
  # Get the response from the cached answers list
@@ -549,7 +561,7 @@ class DnsServer:
549
561
  f'{self.offline_route_ipv6}')
550
562
  )
551
563
 
552
- message = f"!!! Question / Answer is in offline mode returning " \
564
+ message = f"!!! Request / Response is in offline mode returning " \
553
565
  f"{self.offline_route_ipv6}."
554
566
  self.logger.info(message)
555
567
 
@@ -565,7 +577,7 @@ class DnsServer:
565
577
  elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
566
578
  dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
567
579
 
568
- message = f"!!! Question / Answer is in offline mode returning: " \
580
+ message = f"!!! Request / Response is in offline mode returning: " \
569
581
  f"{self.offline_srv_answer}."
570
582
  self.logger.info(message)
571
583
  elif qtype_string == "ANY":
@@ -574,7 +586,7 @@ class DnsServer:
574
586
  self.offline_route_domain)
575
587
  )
576
588
 
577
- message = f"!!! Question / Answer is in offline mode returning " \
589
+ message = f"!!! Request / Response is in offline mode returning " \
578
590
  f"{self.offline_route_domain}."
579
591
  self.logger.info(message)
580
592
  else:
@@ -584,7 +596,7 @@ class DnsServer:
584
596
  " " + self.offline_route_ipv4)
585
597
  )
586
598
 
587
- message = f"!!! Question / Answer is in offline mode returning " \
599
+ message = f"!!! Request / Response is in offline mode returning " \
588
600
  f"{self.offline_route_ipv4}."
589
601
  self.logger.info(message)
590
602
  # Values error means in most cases that you create wrong response
@@ -1,7 +1,7 @@
1
1
  import ssl
2
2
  import functools
3
3
 
4
- from . import base
4
+ from . import socket_base
5
5
  from ...print_api import print_api
6
6
  from ...inspect_wrapper import get_target_function_default_args_and_combine_with_current
7
7
 
@@ -75,18 +75,25 @@ def connection_exception_decorator(function_name):
75
75
  pass
76
76
  pass
77
77
  except ssl.SSLError as exception_object:
78
- # Getting the exact reason of "ssl.SSLError"
79
- if exception_object.reason == "HTTP_REQUEST":
80
- message = f"Socket Accept: HTTP Request on SSL Socket: " \
81
- f"{base.get_source_destination(kwargs['socket_object'])}"
82
- wrapper_handle_connection_exceptions.message = message
83
- print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
84
- elif exception_object.reason == "TSV1_ALERT_UNKNOWN_CA":
85
- message = f"Socket Accept: Check CA certificate on the client " \
86
- f"{base.get_source_destination(kwargs['socket_object'])}"
87
- wrapper_handle_connection_exceptions.message = message
88
- print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
89
- else:
78
+ excepted: bool = False
79
+ if getattr(exception_object, "reason", None):
80
+ # Getting the exact reason of "ssl.SSLError"
81
+ if exception_object.reason == "HTTP_REQUEST":
82
+ message = f"Socket Accept: HTTP Request on SSL Socket: " \
83
+ f"{socket_base.get_source_destination(kwargs['socket_object'])}"
84
+ wrapper_handle_connection_exceptions.message = message
85
+ print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
86
+
87
+ excepted = True
88
+ elif exception_object.reason == "TSV1_ALERT_UNKNOWN_CA":
89
+ message = f"Socket Accept: Check CA certificate on the client " \
90
+ f"{socket_base.get_source_destination(kwargs['socket_object'])}"
91
+ wrapper_handle_connection_exceptions.message = message
92
+ print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
93
+
94
+ excepted = True
95
+
96
+ if not excepted:
90
97
  # Not all requests have the server name passed through Client Hello.
91
98
  # If it is not passed an error of undefined variable will be raised.
92
99
  # So, we'll check if the variable as a string is in the "locals()" variable pool.
@@ -102,21 +109,19 @@ def connection_exception_decorator(function_name):
102
109
  f"Socket Accept: {domain_from_dns_server}:{port}: {message}"
103
110
  wrapper_handle_connection_exceptions.message = message
104
111
  print_api(message, logger_method='error', oneline=True, **kwargs['print_kwargs'])
105
- pass
112
+
106
113
  except FileNotFoundError:
107
114
  message = "'SSLSocket.accept()' crashed: 'FileNotFoundError'. Some problem with SSL during Handshake - " \
108
115
  "Could be certificate, client, or server."
109
116
  message = f"Socket Accept: {domain_from_dns_server}:{port}: {message}"
110
117
  wrapper_handle_connection_exceptions.message = message
111
118
  print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
112
- pass
113
119
  except Exception as e:
114
120
  _ = e
115
121
  message = "Undocumented exception on accept."
116
122
  message = f"Socket Accept: {domain_from_dns_server}:{port}: {message}"
117
123
  wrapper_handle_connection_exceptions.message = message
118
124
  print_api(message, logger_method='error', traceback_string=True, oneline=True, **kwargs['print_kwargs'])
119
- pass
120
125
 
121
126
  wrapper_handle_connection_exceptions.message = None
122
127
  return wrapper_handle_connection_exceptions
@@ -0,0 +1,86 @@
1
+ import logging
2
+
3
+ from . import socket_base
4
+ from ...print_api import print_api
5
+ from ...ssh_remote import SSHRemote
6
+ from ... import package_mains_processor
7
+
8
+
9
+ GET_LOCALHOST_FUNCTION_NAME = 'find_cmdline_by_port'
10
+
11
+
12
+ class GetCommandLine:
13
+ def __init__(
14
+ self,
15
+ client_ip: str,
16
+ client_port: int = None,
17
+ package_processor: package_mains_processor.PackageMainsProcessor = None,
18
+ ssh_client: SSHRemote = None,
19
+ logger: logging.Logger = None
20
+ ):
21
+ self.client_ip: str = client_ip
22
+ self.client_port: int = client_port
23
+ self.package_processor: package_mains_processor.PackageMainsProcessor = package_processor
24
+ self.ssh_client: SSHRemote = ssh_client
25
+ self.logger: logging.Logger = logger
26
+
27
+ def get_process_name(self, print_kwargs: dict = None):
28
+ if print_kwargs is None:
29
+ print_kwargs = {}
30
+
31
+ # Checking if we're on localhost. If not, we'll execute SSH connection to get calling process name.
32
+ if self.client_ip not in socket_base.THIS_DEVICE_IP_LIST:
33
+ # Tried using paramiko SSH concurrently within threads, but with bigger loads it just breaks.
34
+ # So, better using it separately for each thread.
35
+
36
+ print_api(f"Initializing SSH connection to [{self.client_ip}]", **print_kwargs)
37
+
38
+ script_string: str = self.package_processor.read_script_file_to_string()
39
+
40
+ execution_output, execution_error = self.ssh_client.connect_get_client_commandline(port=self.client_port, script_string=script_string)
41
+ # Else, if we're on localhost, then execute the script directly without SSH.
42
+ else:
43
+ print_api(f"Executing LOCALHOST command to get the calling process.", **print_kwargs)
44
+ # execution_output, execution_error, rc = self.package_processor.execute_script_with_subprocess(arguments=[str(client_port)])
45
+ execution_output = self.package_processor.execute_script_file(
46
+ function_name=GET_LOCALHOST_FUNCTION_NAME, args=(self.client_port,))
47
+ execution_error = None
48
+
49
+ # This section is generic for both remote SSH and localhost executions of the script.
50
+ process_name = self.get_commandline_and_error(execution_output, execution_error, print_kwargs=print_kwargs)
51
+
52
+ return process_name
53
+
54
+ @staticmethod
55
+ def get_commandline_and_error(
56
+ execution_output,
57
+ execution_error,
58
+ print_kwargs: dict = None
59
+ ):
60
+ # If there was known error on localhost / known error on remote or any kind of error on remote, it was
61
+ # already logged, so we'll just put the error into 'process_name'.
62
+ if execution_error:
63
+ process_name = execution_error
64
+ print_api(
65
+ f"Error During Command Execution: {process_name}", error_type=True,
66
+ logger_method='error', **(print_kwargs or {}))
67
+ # If there wasn't any error of above types, then we can put the output from either local or remote script
68
+ # execution into 'process_name' and log it / output to console.
69
+ else:
70
+ # If the output that was returned is not empty.
71
+ if execution_output:
72
+ # Replacing '\r\n' escape lines with string, so that the line will not be escaped in logs.
73
+ if '\r\n' in execution_output:
74
+ execution_output = execution_output.replace('\r\n', '')
75
+ elif '\n' in execution_output:
76
+ execution_output = execution_output.replace('\n', '')
77
+
78
+ process_name = execution_output
79
+ print_api(f"Client Process Command Line: {process_name}", **(print_kwargs or {}))
80
+ # Else if the script output came back empty.
81
+ else:
82
+ process_name = ''
83
+ message = "Client Process Command Line came back empty after script execution."
84
+ print_api(message, error_type=True, logger_method='error', **(print_kwargs or {}))
85
+
86
+ return process_name
@@ -1,26 +1,44 @@
1
1
  import logging
2
2
  import socket
3
3
  import ssl
4
- import time
5
4
 
6
5
  import select
7
6
  from pathlib import Path
8
7
 
9
8
  from ...print_api import print_api
9
+ from ...basics import tracebacks
10
10
  from ..loggingw import loggingw
11
11
 
12
12
 
13
- def peek_first_bytes(client_socket, bytes_amount: int = 1) -> bytes:
13
+ def peek_first_bytes(
14
+ client_socket,
15
+ bytes_amount: int = 1,
16
+ timeout: float = None
17
+ ) -> bytes:
14
18
  """
15
19
  Peek first byte from the socket without removing it from the buffer.
16
20
 
17
21
  :param client_socket: Socket object.
18
22
  :param bytes_amount: Amount of bytes to peek.
23
+ :param timeout: float, Timeout in seconds.
19
24
 
20
25
  :return: the first X bytes from the socket buffer.
21
26
  """
22
27
 
23
- return client_socket.recv(bytes_amount, socket.MSG_PEEK)
28
+ error: bool = False
29
+ client_socket.settimeout(timeout)
30
+
31
+ try:
32
+ peek_a_bytes: bytes = client_socket.recv(bytes_amount, socket.MSG_PEEK)
33
+ except socket.timeout:
34
+ error = True
35
+ finally:
36
+ client_socket.settimeout(None)
37
+
38
+ if error:
39
+ raise TimeoutError
40
+
41
+ return peek_a_bytes
24
42
 
25
43
 
26
44
  def is_socket_ready_for_read(socket_instance, timeout: float = 0) -> bool:
@@ -93,14 +111,16 @@ class Receiver:
93
111
  # A signal to close connection will be empty bytes string: b''.
94
112
  received_data = self.ssl_socket.recv(self.buffer_size_receive)
95
113
  except ConnectionAbortedError:
96
- error_message = "ConnectionAbortedError: * Connection was aborted by the other side..."
97
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=False)
114
+ error_message = "ConnectionAbortedError: Connection was aborted by local TCP stack (not remote close)..."
98
115
  except ConnectionResetError:
99
- error_message = "ConnectionResetError: * Connection was forcibly closed by the other side..."
100
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=False)
116
+ error_message = "ConnectionResetError: Connection was forcibly closed by the other side..."
117
+ except TimeoutError as e:
118
+ if e.errno == 10060:
119
+ error_message = "TimeoutError: [WinError 10060] Socket receive operation timed out..."
120
+ else:
121
+ raise e
101
122
  except ssl.SSLError:
102
- error_message = "ssl.SSLError: * Encountered SSL error on receive..."
103
- print_api(error_message, logger=self.logger, logger_method='critical', traceback_string=True)
123
+ error_message = f"ssl.SSLError: Encountered SSL error on receive...\n{tracebacks.get_as_string()}"
104
124
 
105
125
  if received_data == b'':
106
126
  self.logger.info("Empty message received, socket closed on the other side.")
@@ -1,3 +1,4 @@
1
+ import socket
1
2
  import ssl
2
3
  import logging
3
4
  from pathlib import Path
@@ -6,18 +7,18 @@ from ...print_api import print_api
6
7
  from ..loggingw import loggingw
7
8
  from ...basics import tracebacks
8
9
 
9
- from . import base
10
+ from . import socket_base
10
11
 
11
12
 
12
13
  class Sender:
13
14
  def __init__(
14
15
  self,
15
- ssl_socket: ssl.SSLSocket,
16
- class_message: bytes,
16
+ ssl_socket: ssl.SSLSocket | socket.socket,
17
+ bytes_to_send: bytes,
17
18
  logger: logging.Logger = None
18
19
  ):
19
- self.class_message: bytes = class_message
20
- self.ssl_socket: ssl.SSLSocket = ssl_socket
20
+ self.bytes_to_send: bytes = bytes_to_send
21
+ self.ssl_socket: ssl.SSLSocket | socket.socket = ssl_socket
21
22
 
22
23
  if logger:
23
24
  # Create child logger for the provided logger with the module's name.
@@ -42,7 +43,7 @@ class Sender:
42
43
 
43
44
  try:
44
45
  # Getting byte length of current message
45
- current_message_length = len(self.class_message)
46
+ current_message_length = len(self.bytes_to_send)
46
47
 
47
48
  self.logger.info(
48
49
  f"Sending message to "
@@ -51,7 +52,7 @@ class Sender:
51
52
  # Looping through "socket.send()" method while total sent bytes are less than message length
52
53
  while total_sent_bytes < current_message_length:
53
54
  # Sending the message and getting the amount of bytes sent
54
- sent_bytes = self.ssl_socket.send(self.class_message[total_sent_bytes:])
55
+ sent_bytes = self.ssl_socket.send(self.bytes_to_send[total_sent_bytes:])
55
56
  # If there were only "0" bytes sent, then connection on the other side was terminated
56
57
  if sent_bytes == 0:
57
58
  error_message = (
@@ -61,13 +62,13 @@ class Sender:
61
62
  break
62
63
 
63
64
  # Adding amount of currently sent bytes to the total amount of bytes sent
64
- total_sent_bytes = total_sent_bytes + sent_bytes
65
+ total_sent_bytes += sent_bytes
65
66
  self.logger.info(f"Sent {total_sent_bytes} bytes out of {current_message_length}")
66
67
 
67
68
  # At this point the sending loop finished successfully
68
69
  self.logger.info(f"Sent the message to destination.")
69
70
  except Exception as e:
70
- source_tuple, destination_tuple = base.get_source_destination(self.ssl_socket)
71
+ source_tuple, destination_tuple = socket_base.get_source_destination(self.ssl_socket)
71
72
  source_address, source_port = source_tuple
72
73
  destination_address, destination_port = destination_tuple
73
74
  if self.ssl_socket.server_hostname:
@@ -47,7 +47,9 @@ class SNISetup:
47
47
  tls: bool,
48
48
  domain_from_dns_server: str = None,
49
49
  skip_extension_id_list: list = None,
50
- exceptions_logger: loggingw.ExceptionCsvLogger = None
50
+ exceptions_logger: loggingw.ExceptionCsvLogger = None,
51
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
52
+ sslkeylog_file_path: str = None
51
53
  ):
52
54
  self.ca_certificate_name = ca_certificate_name
53
55
  self.ca_certificate_filepath = ca_certificate_filepath
@@ -73,8 +75,9 @@ class SNISetup:
73
75
  self.skip_extension_id_list = skip_extension_id_list
74
76
  self.tls = tls
75
77
  self.exceptions_logger = exceptions_logger
76
-
77
78
  self.certificator_instance = None
79
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
80
+ self.sslkeylog_file_path: str = sslkeylog_file_path
78
81
 
79
82
  def wrap_socket_with_ssl_context_server_sni_extended(
80
83
  self,
@@ -83,7 +86,9 @@ class SNISetup:
83
86
  ):
84
87
 
85
88
  # Create SSL Socket to wrap the raw socket with.
86
- ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server()
89
+ ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(
90
+ allow_legacy=True, enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
91
+ sslkeylog_file_path=self.sslkeylog_file_path)
87
92
 
88
93
  self.certificator_instance = certificator.Certificator(
89
94
  ca_certificate_name=self.ca_certificate_name,
@@ -102,7 +107,9 @@ class SNISetup:
102
107
  forwarding_dns_service_ipv4_list___only_for_localhost=(
103
108
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
104
109
  skip_extension_id_list=self.skip_extension_id_list,
105
- tls=self.tls
110
+ tls=self.tls,
111
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
112
+ sslkeylog_file_path=self.sslkeylog_file_path
106
113
  )
107
114
 
108
115
  # Add SNI callback function to the SSL context.
@@ -150,7 +157,8 @@ class SNISetup:
150
157
  # The function is actually called at "accept()" method of the "ssl.SSLSocket"
151
158
  # This needs to be set only once on the listening socket
152
159
  if self.sni_custom_callback_function:
153
- ssl_context.sni_callback = self.sni_custom_callback_function
160
+ # ssl_context.sni_callback = self.sni_custom_callback_function
161
+ ssl_context.set_servername_callback(self.sni_custom_callback_function)
154
162
 
155
163
  if self.sni_use_default_callback_function:
156
164
  sni_handler_instance = SNIHandler(
@@ -160,8 +168,10 @@ class SNISetup:
160
168
  certificator_instance=self.certificator_instance,
161
169
  domain_from_dns_server=self.domain_from_dns_server,
162
170
  default_certificate_domain_list=self.default_certificate_domain_list,
163
- exceptions_logger=self.exceptions_logger
164
- )
171
+ exceptions_logger=self.exceptions_logger,
172
+ enable_sslkeylogfile_env_to_client_ssl_context=(
173
+ self.certificator_instance.enable_sslkeylogfile_env_to_client_ssl_context),
174
+ sslkeylog_file_path=self.certificator_instance.sslkeylog_file_path)
165
175
  ssl_context.set_servername_callback(
166
176
  sni_handler_instance.setup_sni_callback(print_kwargs=print_kwargs))
167
177
 
@@ -179,7 +189,9 @@ class SNIHandler:
179
189
  certificator_instance: certificator.Certificator,
180
190
  domain_from_dns_server: str,
181
191
  default_certificate_domain_list: list,
182
- exceptions_logger: loggingw.ExceptionCsvLogger
192
+ exceptions_logger: loggingw.ExceptionCsvLogger,
193
+ enable_sslkeylogfile_env_to_client_ssl_context: bool,
194
+ sslkeylog_file_path: str
183
195
  ):
184
196
  self.sni_use_default_callback_function_extended = sni_use_default_callback_function_extended
185
197
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -188,6 +200,8 @@ class SNIHandler:
188
200
  self.domain_from_dns_server: str = domain_from_dns_server
189
201
  self.default_certificate_domain_list = default_certificate_domain_list
190
202
  self.exceptions_logger = exceptions_logger
203
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
204
+ self.sslkeylog_file_path: str = sslkeylog_file_path
191
205
 
192
206
  # noinspection PyTypeChecker
193
207
  self.sni_received_parameters: SNIReceivedParameters = None
@@ -322,8 +336,15 @@ class SNIHandler:
322
336
  # Since new default certificate was created we need to create new SSLContext and add the certificate.
323
337
  # You need to build new context and exchange the context that being inherited from the main socket,
324
338
  # or else the context will receive previous certificate each time.
325
- self.sni_received_parameters.ssl_socket.context = \
326
- creator.create_server_ssl_context___load_certificate_and_key(default_server_certificate_path, None)
339
+ self.sni_received_parameters.ssl_socket.context = (
340
+ creator.create_server_ssl_context___load_certificate_and_key(
341
+ default_server_certificate_path,
342
+ None,
343
+ inherit_from=self.sni_received_parameters.ssl_socket.context,
344
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
345
+ sslkeylog_file_path=self.sslkeylog_file_path
346
+ )
347
+ )
327
348
  else:
328
349
  message = f"Couldn't create / overwrite Default Server Certificate: {default_server_certificate_path}"
329
350
  raise SNIDefaultCertificateCreationError(message)