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

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '3.2.13'
4
+ __version__ = '3.3.1'
@@ -7,10 +7,77 @@ import concurrent.futures
7
7
  from concurrent.futures import ProcessPoolExecutor, as_completed
8
8
  from collections import deque
9
9
  from typing import Callable
10
+ import time
10
11
 
11
12
  from ..import system_resources
12
13
 
13
14
 
15
+ def kill_processes(
16
+ processes: list
17
+ ):
18
+ """Terminate all children with SIGTERM (or SIGKILL if you like)."""
19
+ # Ask OS to terminate all processes in the list.
20
+ for p in processes:
21
+ if p.is_alive():
22
+ p.terminate()
23
+ time.sleep(1) # give processes a chance to exit cleanly
24
+ # Force kill all processes in the list.
25
+ for p in processes:
26
+ if p.is_alive():
27
+ p.kill()
28
+ for p in processes: # wait for everything to disappear
29
+ p.join()
30
+
31
+
32
+ def is_process_crashed(
33
+ processes: list[multiprocessing.Process]
34
+ ) -> tuple[int, str] | tuple[None, None]:
35
+ """
36
+ Check if any of the processes in the list is not alive.
37
+ :param processes: list, list of multiprocessing.Process objects.
38
+ :return: tuple(int, string) or None.
39
+ tuple(0 if any finished cleanly, process name).
40
+ tuple(1 (or exit code integer) if any process crashed, process_name).
41
+ None if all processes are still alive.
42
+
43
+ ==============================================
44
+
45
+ Usage example:
46
+ processes = [multiprocessing.Process(target=some_function) for _ in range(5)]
47
+
48
+ for p in processes:
49
+ p.start()
50
+
51
+ # Check if any process has crashed
52
+ try:
53
+ while True:
54
+ # Poll every second; you can use a shorter sleep if you prefer.
55
+ result, process_name = is_process_crashed(processes)
56
+ # If result is None, all processes are still alive.
57
+ if result is not None:
58
+ # If result is 0 or 1, we can exit the loop.
59
+ print(f"Process [{process_name}] finished with exit code {result}.")
60
+ break
61
+ time.sleep(1)
62
+ except KeyboardInterrupt:
63
+ print("Ctrl-C caught – terminating children…")
64
+ kill_all(processes)
65
+ sys.exit(0)
66
+ """
67
+
68
+ for p in processes:
69
+ if p.exitcode is not None: # the process is *dead*
70
+ kill_processes(processes) # stop the rest
71
+ if p.exitcode == 0:
72
+ # print(f"{p.name} exited cleanly; shutting down.")
73
+ return 0, p.name
74
+ else:
75
+ # print(f"{p.name} crashed (exitcode {p.exitcode}). Shutting everything down.")
76
+ return p.exitcode, p.name
77
+
78
+ return None, None # all processes are still alive
79
+
80
+
14
81
  def process_wrap_queue(function_reference: Callable, *args, **kwargs):
15
82
  """
16
83
  The function receives function reference and arguments, and executes the function in a thread.
atomicshop/config_init.py CHANGED
@@ -8,7 +8,11 @@ CONFIG_FILE_NAME = 'config.toml'
8
8
  CONFIG: dict = dict()
9
9
 
10
10
 
11
- def get_config(script_directory: str = None, config_file_name: str = CONFIG_FILE_NAME) -> dict:
11
+ def get_config(
12
+ script_directory: str = None,
13
+ config_file_name: str = CONFIG_FILE_NAME,
14
+ print_kwargs: dict = None
15
+ ) -> dict:
12
16
  """
13
17
  Get the config file content.
14
18
 
@@ -16,6 +20,7 @@ def get_config(script_directory: str = None, config_file_name: str = CONFIG_FILE
16
20
  get the working directory instead.
17
21
  :param config_file_name: string, name of the config file. Default is 'config.toml' as specified in the constant:
18
22
  'CONFIG_FILE_NAME'.
23
+ :param print_kwargs: dict, additional arguments to pass to the print function. Default is None.
19
24
  :return: dict.
20
25
  """
21
26
 
@@ -25,7 +30,7 @@ def get_config(script_directory: str = None, config_file_name: str = CONFIG_FILE
25
30
  if not script_directory:
26
31
  script_directory = filesystem.get_working_directory()
27
32
 
28
- CONFIG = tomls.read_toml_file(f'{script_directory}{os.sep}{config_file_name}')
33
+ CONFIG = tomls.read_toml_file(f'{script_directory}{os.sep}{config_file_name}', **(print_kwargs or {}))
29
34
  return CONFIG
30
35
 
31
36
 
@@ -161,7 +161,10 @@ class ProcessName:
161
161
  ssh_script_to_execute: Literal['process_from_port', 'process_from_ipv4'] = 'process_from_port'
162
162
 
163
163
 
164
- def load_config(config_toml_file_path: str):
164
+ def load_config(
165
+ config_toml_file_path: str,
166
+ print_kwargs: dict = None
167
+ ):
165
168
  # global CONFIG
166
169
 
167
170
  script_path = os.path.dirname(config_toml_file_path)
@@ -169,11 +172,11 @@ def load_config(config_toml_file_path: str):
169
172
  MainConfig.update()
170
173
 
171
174
  # Load the configuration file.
172
- result = import_config.import_config_files(config_toml_file_path)
175
+ result = import_config.import_config_files(config_toml_file_path, print_kwargs=print_kwargs or {})
173
176
  return result
174
177
 
175
178
 
176
- def get_listening_addresses(client_message: ClientMessage) -> dict:
179
+ def get_listening_addresses(client_message: ClientMessage) -> dict | None:
177
180
  """
178
181
  Get the list of listening addresses from the TCPServer configuration.
179
182
  If no_engines_usage_to_listen_addresses_enable is True, return the no_engines_listening_address_list.
@@ -10,20 +10,25 @@ from ..basics import threads, tracebacks
10
10
  from ..print_api import print_api
11
11
 
12
12
  from .message import ClientMessage
13
- from . import config_static, initialize_engines
13
+ from . import initialize_engines
14
+ from ..wrappers.loggingw import loggingw
15
+ # This is needed only for the data typing.
16
+ from . import config_static as cf
14
17
 
15
18
 
16
19
  def thread_worker_main(
20
+ # These parameters come from the SocketWrapper.
17
21
  client_socket,
18
22
  process_commandline: str,
19
23
  is_tls: bool,
20
24
  tls_type: str,
21
25
  tls_version: str,
22
26
  domain_from_dns,
23
- network_logger,
24
27
  statistics_writer,
25
28
  engines_list,
26
- reference_module
29
+
30
+ # These parameters come from the main mitm module.
31
+ config_static: cf
27
32
  ):
28
33
  def output_statistics_csv_row(client_message: ClientMessage):
29
34
  # If there is no '.code' attribute in HTTPResponse, this means that this is not an HTTP message, so there is no
@@ -543,6 +548,8 @@ def thread_worker_main(
543
548
  # ================================================================================================================
544
549
  # This is the start of the thread_worker_main function
545
550
 
551
+ network_logger = loggingw.get_logger_with_level(config_static.MainConfig.LOGGER_NAME)
552
+
546
553
  # Only protocols that are encrypted with TLS have the server name attribute.
547
554
  if is_tls:
548
555
  # Get current destination domain
@@ -568,7 +575,7 @@ def thread_worker_main(
568
575
  found_domain_module = initialize_engines.assign_class_by_domain(
569
576
  engines_list=engines_list,
570
577
  message_domain_name=server_name,
571
- reference_module=reference_module
578
+ reference_module=config_static.REFERENCE_MODULE
572
579
  )
573
580
  parser = found_domain_module.parser_class_object
574
581
  requester = found_domain_module.requester_class_object()
@@ -212,3 +212,24 @@ class ResponderGeneral(ResponderParent):
212
212
  #
213
213
  # result_response_list: list[bytes] = [byte_response]
214
214
  # return result_response_list
215
+ #
216
+ # ==================================================================================================================
217
+ # TEST RESPONSE.
218
+ # def create_response(self, class_client_message: ClientMessage):
219
+ # resp_body_text: bytes = b"<html><body>TEST OK!</body></html>\n"
220
+ # resp_status_code: int = 200
221
+ # resp_headers: dict = {
222
+ # # Tell the browser it’s plain text (could be “text/html” if you wrap it in HTML).
223
+ # "Content-Type": "text/html; charset=utf-8"}
224
+ #
225
+ # # Build the raw bytes to send.
226
+ # byte_response = self.build_byte_response(
227
+ # http_version="HTTP/1.1",
228
+ # status_code=resp_status_code,
229
+ # headers=resp_headers,
230
+ # body=resp_body_text
231
+ #
232
+ # )
233
+ #
234
+ # result_response_list: list[bytes] = [byte_response]
235
+ # return result_response_list
@@ -35,17 +35,22 @@ def assign_bool(dict_instance: dict, section: str, key: str):
35
35
 
36
36
 
37
37
  def import_config_files(
38
- config_file_path: str
38
+ config_file_path: str,
39
+ print_kwargs: dict = None
39
40
  ):
40
41
  """
41
42
  Import the configuration file 'config.toml' and write all the values to 'config_static' dataclasses module.
42
43
 
43
44
  :param config_file_path:
45
+ :param print_kwargs: dict, additional arguments to pass to the print function.
44
46
  :return:
45
47
  """
46
48
 
47
49
  config_toml: dict = config_init.get_config(
48
- script_directory=str(Path(config_file_path).parent), config_file_name=Path(config_file_path).name)
50
+ script_directory=str(Path(config_file_path).parent),
51
+ config_file_name=Path(config_file_path).name,
52
+ print_kwargs=print_kwargs or {}
53
+ )
49
54
 
50
55
  # Assign boolean values to the toml dict module.
51
56
  for boolean_tuple in config_static.LIST_OF_BOOLEANS:
@@ -61,7 +66,7 @@ def import_config_files(
61
66
 
62
67
  manipulations_after_import()
63
68
 
64
- result = import_engines_configs()
69
+ result = import_engines_configs(print_kwargs=print_kwargs or {})
65
70
  if result != 0:
66
71
  return result
67
72
 
@@ -69,7 +74,7 @@ def import_config_files(
69
74
  return result
70
75
 
71
76
 
72
- def import_engines_configs() -> int:
77
+ def import_engines_configs(print_kwargs: dict) -> int:
73
78
  """
74
79
  Import the engines configuration files and write all the values to 'config_static' dataclasses module.
75
80
 
@@ -88,8 +93,8 @@ def import_engines_configs() -> int:
88
93
  for engine_config_path in engine_config_path_list:
89
94
  # Initialize engine.
90
95
  current_module: initialize_engines.ModuleCategory = initialize_engines.ModuleCategory(config_static.MainConfig.SCRIPT_DIRECTORY)
91
- current_module.fill_engine_fields_from_config(engine_config_path.path)
92
- result_code, error = current_module.initialize_engine()
96
+ current_module.fill_engine_fields_from_config(engine_config_path.path, print_kwargs=print_kwargs or {})
97
+ result_code, error = current_module.initialize_engine(print_kwargs=print_kwargs or {})
93
98
  if result_code != 0:
94
99
  print_api(f"Error initializing engine from directory: {Path(engine_config_path.path).parent}\n{error}", color='red')
95
100
  return result_code
@@ -344,6 +349,7 @@ def manipulations_after_import():
344
349
  config_static.Certificates.custom_private_key_path = filesystem.check_absolute_path___add_full(
345
350
  config_static.Certificates.custom_private_key_path, config_static.MainConfig.SCRIPT_DIRECTORY)
346
351
  else:
352
+ # noinspection PyTypeChecker
347
353
  config_static.Certificates.custom_private_key_path = None
348
354
 
349
355
  config_static.Certificates.sni_server_certificates_cache_directory = filesystem.check_absolute_path___add_full(
@@ -41,9 +41,13 @@ class ModuleCategory:
41
41
  self.responder_file_path = reference_folder_path + os.sep + "responder___reference_general.py"
42
42
  self.recorder_file_path = reference_folder_path + os.sep + "recorder___reference_general.py"
43
43
 
44
- def fill_engine_fields_from_config(self, engine_config_file_path: str):
44
+ def fill_engine_fields_from_config(
45
+ self,
46
+ engine_config_file_path: str,
47
+ print_kwargs: dict = None
48
+ ):
45
49
  # Read the configuration file of the engine.
46
- configuration_data = tomls.read_toml_file(engine_config_file_path)
50
+ configuration_data = tomls.read_toml_file(engine_config_file_path, **(print_kwargs or {}))
47
51
 
48
52
  engine_directory_path: str = str(Path(engine_config_file_path).parent)
49
53
  self.engine_name = Path(engine_directory_path).name
@@ -92,17 +96,21 @@ class ModuleCategory:
92
96
  for subdomain, file_name in self.mtls.items():
93
97
  self.mtls[subdomain] = f'{engine_directory_path}{os.sep}{file_name}'
94
98
 
95
- def initialize_engine(self, reference_general: bool = False) -> tuple[int, str]:
99
+ def initialize_engine(
100
+ self,
101
+ reference_general: bool = False,
102
+ print_kwargs: dict = None
103
+ ) -> tuple[int, str]:
96
104
  try:
97
105
  if not reference_general:
98
106
  self.parser_class_object = import_first_class_name_from_file_path(
99
- self.script_directory, self.parser_file_path)
107
+ self.script_directory, self.parser_file_path, **(print_kwargs or {}))
100
108
  self.requester_class_object = import_first_class_name_from_file_path(
101
- self.script_directory, self.requester_file_path)
109
+ self.script_directory, self.requester_file_path, **(print_kwargs or {}))
102
110
  self.responder_class_object = import_first_class_name_from_file_path(
103
- self.script_directory, self.responder_file_path)
111
+ self.script_directory, self.responder_file_path, **(print_kwargs or {}))
104
112
  self.recorder_class_object = import_first_class_name_from_file_path(
105
- self.script_directory, self.recorder_file_path)
113
+ self.script_directory, self.recorder_file_path, **(print_kwargs or {}))
106
114
  else:
107
115
  self.parser_class_object = parser___reference_general.ParserGeneral
108
116
  self.requester_class_object = requester___reference_general.RequesterGeneral