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 +1 -1
- atomicshop/basics/multiprocesses.py +67 -0
- atomicshop/config_init.py +7 -2
- atomicshop/mitm/config_static.py +6 -3
- atomicshop/mitm/connection_thread_worker.py +11 -4
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +21 -0
- atomicshop/mitm/import_config.py +12 -6
- atomicshop/mitm/initialize_engines.py +15 -7
- atomicshop/mitm/mitm_main.py +268 -150
- atomicshop/wrappers/loggingw/loggingw.py +464 -33
- atomicshop/wrappers/socketw/dns_server.py +28 -10
- atomicshop/wrappers/socketw/socket_client.py +7 -0
- atomicshop/wrappers/socketw/socket_wrapper.py +164 -172
- atomicshop/wrappers/socketw/statistics_csv.py +32 -13
- atomicshop/wrappers/ubuntu_terminal.py +11 -2
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/METADATA +1 -1
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/RECORD +21 -21
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/LICENSE.txt +0 -0
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/WHEEL +0 -0
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/entry_points.txt +0 -0
- {atomicshop-3.2.13.dist-info → atomicshop-3.3.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -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(
|
|
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
|
|
atomicshop/mitm/config_static.py
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
atomicshop/mitm/import_config.py
CHANGED
|
@@ -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),
|
|
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(
|
|
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(
|
|
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
|