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.
- atomicshop/__init__.py +1 -1
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/basics/strings.py +1 -1
- atomicshop/certificates.py +2 -2
- atomicshop/dns.py +26 -28
- atomicshop/etws/traces/trace_tcp.py +1 -2
- atomicshop/mitm/centered_settings.py +133 -0
- atomicshop/mitm/config_static.py +22 -44
- atomicshop/mitm/connection_thread_worker.py +383 -165
- atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
- atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
- atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
- atomicshop/mitm/engines/create_module_template.py +1 -2
- atomicshop/mitm/import_config.py +91 -89
- atomicshop/mitm/initialize_engines.py +1 -2
- atomicshop/mitm/message.py +5 -4
- atomicshop/mitm/mitm_main.py +238 -122
- atomicshop/mitm/recs_files.py +61 -5
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +33 -12
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
- atomicshop/networks.py +160 -92
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/ubuntu_permissions.py +47 -0
- atomicshop/print_api.py +3 -5
- atomicshop/process.py +11 -4
- atomicshop/python_functions.py +23 -108
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +140 -164
- atomicshop/web.py +63 -22
- atomicshop/web_apis/google_llm.py +22 -14
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
- atomicshop/wrappers/dockerw/dockerw.py +2 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
- atomicshop/wrappers/githubw.py +180 -68
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/handlers.py +1 -1
- atomicshop/wrappers/loggingw/loggingw.py +20 -4
- atomicshop/wrappers/loggingw/reading.py +18 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
- atomicshop/wrappers/netshw.py +124 -3
- atomicshop/wrappers/playwrightw/scenarios.py +1 -1
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
- atomicshop/wrappers/socketw/certificator.py +19 -9
- atomicshop/wrappers/socketw/creator.py +101 -14
- atomicshop/wrappers/socketw/dns_server.py +17 -5
- atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +29 -9
- atomicshop/wrappers/socketw/sender.py +10 -9
- atomicshop/wrappers/socketw/sni.py +31 -10
- atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
- atomicshop/wrappers/socketw/socket_client.py +11 -10
- atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
- atomicshop/wrappers/socketw/ssl_base.py +6 -2
- atomicshop/wrappers/ubuntu_terminal.py +21 -18
- atomicshop/wrappers/win_auditw.py +189 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
- atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
- atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
- atomicshop/a_installs/ubuntu/mongodb.py +0 -12
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/__init__.py +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/search_in_archive.py +0 -284
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -73
- atomicshop/archiver/shutils.py +0 -34
- atomicshop/archiver/zips.py +0 -353
- atomicshop/file_types.py +0 -24
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/script_as_string_processor.py +0 -42
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -449
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/__init__.py +0 -0
- atomicshop/wrappers/fibratusw/install.py +0 -80
- atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
- atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/get_process.py +0 -123
- atomicshop/wrappers/wslw.py +0 -192
- atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
atomicshop/mitm/mitm_main.py
CHANGED
|
@@ -5,21 +5,31 @@ import datetime
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
import logging
|
|
8
|
+
import signal
|
|
8
9
|
|
|
9
10
|
import atomicshop # Importing atomicshop package to get the version of the package.
|
|
10
11
|
|
|
11
12
|
from .. import filesystem, on_exit, print_api, networks, dns
|
|
12
13
|
from ..permissions import permissions
|
|
13
|
-
from ..
|
|
14
|
-
from ..wrappers.socketw import socket_wrapper, dns_server,
|
|
14
|
+
from .. import python_functions
|
|
15
|
+
from ..wrappers.socketw import socket_wrapper, dns_server, statistics_csv
|
|
15
16
|
from ..wrappers.loggingw import loggingw
|
|
16
17
|
from ..wrappers.ctyping import win_console
|
|
18
|
+
from ..wrappers import netshw
|
|
17
19
|
from ..basics import multiprocesses
|
|
18
20
|
|
|
19
21
|
from .connection_thread_worker import thread_worker_main
|
|
20
22
|
from . import config_static, recs_files
|
|
21
23
|
|
|
22
24
|
|
|
25
|
+
# If you have 'pip-system-certs' package installed, this section overrides this behavior, since it injects
|
|
26
|
+
# the ssl default behavior, which we don't need when using ssl and sockets.
|
|
27
|
+
import ssl, importlib
|
|
28
|
+
if getattr(ssl.SSLContext.wrap_socket, "__module__", "").startswith("pip._vendor.truststore"):
|
|
29
|
+
# Truststore injection is active; restore stdlib ssl
|
|
30
|
+
importlib.reload(ssl)
|
|
31
|
+
|
|
32
|
+
|
|
23
33
|
class NetworkSettings:
|
|
24
34
|
"""
|
|
25
35
|
Class to store network settings.
|
|
@@ -27,6 +37,7 @@ class NetworkSettings:
|
|
|
27
37
|
|
|
28
38
|
def __init__(
|
|
29
39
|
self,
|
|
40
|
+
name: str | None = None,
|
|
30
41
|
description: str | None = None,
|
|
31
42
|
interface_index: int | None = None,
|
|
32
43
|
is_dynamic: bool = False,
|
|
@@ -37,7 +48,7 @@ class NetworkSettings:
|
|
|
37
48
|
default_gateways: list[str] = None,
|
|
38
49
|
dns_gateways: list[str] = None
|
|
39
50
|
):
|
|
40
|
-
|
|
51
|
+
self.name: str | None = name
|
|
41
52
|
self.description: str | None = description
|
|
42
53
|
self.interface_index: int | None = interface_index
|
|
43
54
|
self.is_dynamic: bool = is_dynamic
|
|
@@ -51,8 +62,6 @@ class NetworkSettings:
|
|
|
51
62
|
|
|
52
63
|
# Global variables for setting the network interface to external IPs (eg: 192.168.0.1)
|
|
53
64
|
NETWORK_INTERFACE_SETTINGS: NetworkSettings = NetworkSettings()
|
|
54
|
-
CURRENT_IPV4S: list[str] = list()
|
|
55
|
-
CURRENT_IPV4_MASKS: list[str] = list()
|
|
56
65
|
IPS_TO_ASSIGN: list[str] = list()
|
|
57
66
|
MASKS_TO_ASSIGN: list[str] = list()
|
|
58
67
|
|
|
@@ -87,41 +96,37 @@ except win_console.NotWindowsConsoleError:
|
|
|
87
96
|
pass
|
|
88
97
|
|
|
89
98
|
|
|
99
|
+
# noinspection PyUnusedLocal
|
|
100
|
+
def _graceful_shutdown(signum, frame):
|
|
101
|
+
exit_cleanup()
|
|
102
|
+
|
|
103
|
+
|
|
90
104
|
def exit_cleanup():
|
|
91
|
-
if config_static.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if is_dns_dynamic != NETWORK_INTERFACE_IS_DYNAMIC or \
|
|
98
|
-
(not is_dns_dynamic and current_dns_gateway != NETWORK_INTERFACE_IPV4_ADDRESS_LIST):
|
|
99
|
-
if NETWORK_INTERFACE_IS_DYNAMIC:
|
|
100
|
-
dns.set_connection_dns_gateway_dynamic(use_default_connection=True)
|
|
101
|
-
else:
|
|
102
|
-
dns.set_connection_dns_gateway_static(
|
|
103
|
-
dns_servers=NETWORK_INTERFACE_IPV4_ADDRESS_LIST, use_default_connection=True)
|
|
104
|
-
|
|
105
|
-
print_api.print_api("Returned default DNS gateway...", color='blue')
|
|
106
|
-
else:
|
|
107
|
-
# Get current network interface state.
|
|
108
|
-
default_network_adapter_config, default_network_adapter, default_adapter_info = networks.get_wmi_network_adapter_configuration(
|
|
109
|
-
use_default_interface=True, get_info_from_network_config=True)
|
|
105
|
+
if not config_static.MainConfig.is_localhost:
|
|
106
|
+
# Remove all the virtual IPs from the interface.
|
|
107
|
+
current_virtual_ips: list[str] = networks.get_interface_ips_powershell(NETWORK_INTERFACE_SETTINGS.name, "virtual")
|
|
108
|
+
for ip in current_virtual_ips:
|
|
109
|
+
netshw.remove_virtual_ip(NETWORK_INTERFACE_SETTINGS.name, ip)
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
# If the network interface was dynamic before the script started, we will return it to dynamic.
|
|
113
|
-
networks.set_dynamic_ip_for_adapter(default_network_adapter_config)
|
|
114
|
-
else:
|
|
115
|
-
networks.set_static_ip_for_adapter(
|
|
116
|
-
default_network_adapter,
|
|
117
|
-
ips=NETWORK_INTERFACE_SETTINGS.ipv4s,
|
|
118
|
-
masks=NETWORK_INTERFACE_SETTINGS.ipv4_subnet_masks,
|
|
119
|
-
gateways=NETWORK_INTERFACE_SETTINGS.default_gateways,
|
|
120
|
-
dns_gateways=NETWORK_INTERFACE_SETTINGS.dns_gateways
|
|
121
|
-
)
|
|
111
|
+
netshw.disable_dhcp_static_coexistence(interface_name=NETWORK_INTERFACE_SETTINGS.name)
|
|
122
112
|
|
|
123
113
|
print_api.print_api("Returned network adapter settings...", color='blue')
|
|
124
114
|
|
|
115
|
+
if permissions.is_admin() and IS_SET_DNS_GATEWAY:
|
|
116
|
+
is_dns_dynamic, current_dns_gateway = dns.get_default_dns_gateway()
|
|
117
|
+
status_string = 'Dynamic' if is_dns_dynamic else 'Static'
|
|
118
|
+
print_api.print_api(f'Current DNS Gateway: {status_string}, {current_dns_gateway}')
|
|
119
|
+
|
|
120
|
+
if is_dns_dynamic != NETWORK_INTERFACE_IS_DYNAMIC or \
|
|
121
|
+
(not is_dns_dynamic and current_dns_gateway != NETWORK_INTERFACE_IPV4_ADDRESS_LIST):
|
|
122
|
+
if NETWORK_INTERFACE_IS_DYNAMIC:
|
|
123
|
+
dns.set_interface_dns_gateway_dynamic(interface_name=NETWORK_INTERFACE_SETTINGS.name)
|
|
124
|
+
else:
|
|
125
|
+
dns.set_interface_dns_gateway_static(
|
|
126
|
+
dns_servers=NETWORK_INTERFACE_IPV4_ADDRESS_LIST, interface_name=NETWORK_INTERFACE_SETTINGS.name)
|
|
127
|
+
|
|
128
|
+
print_api.print_api("Returned default DNS gateway...", color='blue')
|
|
129
|
+
|
|
125
130
|
# The process will not be executed if there was an exception in the beginning.
|
|
126
131
|
if RECS_PROCESS_INSTANCE is not None:
|
|
127
132
|
print_api.print_api(f'Recs archive process alive: {RECS_PROCESS_INSTANCE.is_alive()}')
|
|
@@ -148,7 +153,7 @@ def startup_output(system_logger, script_version: str):
|
|
|
148
153
|
# Writing first log.
|
|
149
154
|
system_logger.info("======================================")
|
|
150
155
|
system_logger.info("Server Started.")
|
|
151
|
-
system_logger.info(f"Python Version: {
|
|
156
|
+
system_logger.info(f"Python Version: {python_functions.get_python_version_string()}")
|
|
152
157
|
system_logger.info(f"Script Version: {script_version}")
|
|
153
158
|
system_logger.info(f"Atomic Workshop Version: {atomicshop.__version__}")
|
|
154
159
|
system_logger.info(f"Log folder: {config_static.LogRec.logs_path}")
|
|
@@ -162,7 +167,7 @@ def startup_output(system_logger, script_version: str):
|
|
|
162
167
|
f"Default server certificate usage enabled, if no SNI available: "
|
|
163
168
|
f"{config_static.MainConfig.default_server_certificate_filepath}")
|
|
164
169
|
|
|
165
|
-
if config_static.Certificates.
|
|
170
|
+
if config_static.Certificates.sni_create_server_certificate_for_each_domain:
|
|
166
171
|
system_logger.info(
|
|
167
172
|
f"SNI function certificates creation enabled. Certificates cache: "
|
|
168
173
|
f"{config_static.Certificates.sni_server_certificates_cache_directory}")
|
|
@@ -185,6 +190,7 @@ def startup_output(system_logger, script_version: str):
|
|
|
185
190
|
# Printing the parsers using "start=1" for index to start counting from "1" and not "0"
|
|
186
191
|
system_logger.info("Imported engine info.")
|
|
187
192
|
print_api.print_api(f"[*] Found Engines:", logger=system_logger)
|
|
193
|
+
print_api.print_api( f"-------------------------", logger=system_logger)
|
|
188
194
|
|
|
189
195
|
if not config_static.ENGINES_LIST:
|
|
190
196
|
message = \
|
|
@@ -199,7 +205,6 @@ def startup_output(system_logger, script_version: str):
|
|
|
199
205
|
f"{engine.responder_class_object.__name__}, "
|
|
200
206
|
f"{engine.recorder_class_object.__name__}")
|
|
201
207
|
print_api.print_api(message, logger=system_logger)
|
|
202
|
-
print_api.print_api(f"[*] Name: {engine.engine_name}", logger=system_logger)
|
|
203
208
|
print_api.print_api(f"[*] Domains: {list(engine.domain_target_dict.keys())}", logger=system_logger)
|
|
204
209
|
dns_targets: list = list()
|
|
205
210
|
for domain, ip_port in engine.domain_target_dict.items():
|
|
@@ -210,9 +215,11 @@ def startup_output(system_logger, script_version: str):
|
|
|
210
215
|
print_api.print_api(f"[*] Connect Ports to IPs: {list(engine.on_port_connect.values())}", logger=system_logger)
|
|
211
216
|
print_api.print_api(f"[*] Connect Ports to IPs Targets: {list(engine.port_target_dict.values())}", logger=system_logger)
|
|
212
217
|
|
|
218
|
+
print_api.print_api("-------------------------", logger=system_logger)
|
|
219
|
+
|
|
213
220
|
# print_api.print_api(f"[*] TCP Listening Interfaces: {engine.tcp_listening_address_list}", logger=system_logger)
|
|
214
221
|
|
|
215
|
-
if config_static.DNSServer.
|
|
222
|
+
if config_static.DNSServer.is_enabled:
|
|
216
223
|
print_api.print_api("DNS Server is enabled.", logger=system_logger)
|
|
217
224
|
|
|
218
225
|
# If engines were found and dns is set to route by the engine domains.
|
|
@@ -236,13 +243,42 @@ def startup_output(system_logger, script_version: str):
|
|
|
236
243
|
else:
|
|
237
244
|
print_api.print_api("DNS Server is disabled.", logger=system_logger, color="yellow")
|
|
238
245
|
|
|
239
|
-
if config_static.TCPServer.
|
|
246
|
+
if config_static.TCPServer.is_enabled:
|
|
240
247
|
print_api.print_api("TCP Server is enabled.", logger=system_logger)
|
|
241
248
|
else:
|
|
242
249
|
print_api.print_api("TCP Server is disabled.", logger=system_logger, color="yellow")
|
|
243
250
|
|
|
251
|
+
if config_static.MainConfig.is_localhost:
|
|
252
|
+
selected_net_interface: str = config_static.MainConfig.network_interface
|
|
253
|
+
else:
|
|
254
|
+
selected_net_interface: str = NETWORK_INTERFACE_SETTINGS.name
|
|
255
|
+
print_api.print_api(f"Selected Network Interface: {selected_net_interface}", logger=system_logger)
|
|
256
|
+
|
|
257
|
+
print_api.print_api(f"Listening DNS address: {config_static.DNSServer.listening_address}", logger=system_logger)
|
|
244
258
|
|
|
245
|
-
|
|
259
|
+
|
|
260
|
+
def _get_interface_name() -> str | None:
|
|
261
|
+
if config_static.MainConfig.network_interface == '':
|
|
262
|
+
interface_name: str = networks.get_default_interface_name()
|
|
263
|
+
if interface_name == '':
|
|
264
|
+
print_api.print_api(
|
|
265
|
+
"Default network interface not found.",
|
|
266
|
+
error_type=True, color="red")
|
|
267
|
+
return None
|
|
268
|
+
else:
|
|
269
|
+
current_network_interface_names: list[str] = networks.list_network_interfaces()
|
|
270
|
+
if config_static.MainConfig.network_interface not in current_network_interface_names:
|
|
271
|
+
print_api.print_api(
|
|
272
|
+
f"Not found Network interface with the name: {config_static.MainConfig.network_interface}",
|
|
273
|
+
error_type=True, color="red")
|
|
274
|
+
return None
|
|
275
|
+
else:
|
|
276
|
+
interface_name = config_static.MainConfig.network_interface
|
|
277
|
+
|
|
278
|
+
return interface_name
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def get_ipv4s_for_tcp_server() -> int:
|
|
246
282
|
"""
|
|
247
283
|
Function to get the IPv4 addresses for the default network adapter to set them to the adapter.
|
|
248
284
|
"""
|
|
@@ -255,48 +291,82 @@ def get_ipv4s_for_tcp_server():
|
|
|
255
291
|
ports_to_create_ips_for += list(engine.on_port_connect.keys())
|
|
256
292
|
|
|
257
293
|
engine_ips: list[str] = list()
|
|
258
|
-
|
|
259
|
-
if config_static.ENGINES_LIST[0].is_localhost:
|
|
260
|
-
create_ips: int = len(domains_to_create_ips_for) + len(ports_to_create_ips_for)
|
|
294
|
+
create_ips: int = len(domains_to_create_ips_for) + len(ports_to_create_ips_for)
|
|
261
295
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
# Get current network interface state.
|
|
267
|
-
default_network_adapter_config, default_network_adapter, default_adapter_info = networks.get_wmi_network_adapter_configuration(
|
|
268
|
-
use_default_interface=True, get_info_from_network_config=True)
|
|
269
|
-
|
|
270
|
-
global NETWORK_INTERFACE_SETTINGS
|
|
271
|
-
NETWORK_INTERFACE_SETTINGS = NetworkSettings(
|
|
272
|
-
description=default_adapter_info['description'],
|
|
273
|
-
interface_index=default_adapter_info['interface_index'],
|
|
274
|
-
is_dynamic=default_adapter_info['is_dynamic'],
|
|
275
|
-
ipv4s=default_adapter_info['ipv4s'],
|
|
276
|
-
ipv6s=default_adapter_info['ipv6s'],
|
|
277
|
-
ipv4_subnet_masks=default_adapter_info['ipv4_subnet_masks'],
|
|
278
|
-
ipv6_prefixes=default_adapter_info['ipv6_prefixes'],
|
|
279
|
-
default_gateways=default_adapter_info['default_gateways'],
|
|
280
|
-
dns_gateways=default_adapter_info['dns_gateways']
|
|
281
|
-
)
|
|
296
|
+
# Get network interface name.
|
|
297
|
+
interface_name: str = _get_interface_name()
|
|
298
|
+
if interface_name is None:
|
|
299
|
+
return 1
|
|
282
300
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
301
|
+
# Get selected network interface virtual IPs from previous runs.
|
|
302
|
+
# We still need network interface settings for DNS gateway assignment for the network interface doesn't matter in localhost mode or not.
|
|
303
|
+
current_virtual_ips: list[str] = networks.get_interface_ips_powershell(interface_name, "virtual")
|
|
286
304
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
create_ips -= current_ips_count
|
|
305
|
+
if current_virtual_ips:
|
|
306
|
+
print_api.print_api(
|
|
307
|
+
f"Removing previous virtual IPs from interface [{interface_name}]: {current_virtual_ips}",
|
|
308
|
+
color="blue")
|
|
292
309
|
|
|
310
|
+
if not permissions.is_admin():
|
|
311
|
+
print_api.print_api(
|
|
312
|
+
f"Administrator permissions are required to remove virtual IPs from interface.",
|
|
313
|
+
error_type=True, color="red")
|
|
314
|
+
return 1
|
|
315
|
+
# Remove all the virtual IPs from the interface.
|
|
316
|
+
for ip in current_virtual_ips:
|
|
317
|
+
netshw.remove_virtual_ip(interface_name, ip)
|
|
318
|
+
|
|
319
|
+
network_adapter_config, network_adapter, adapter_info = networks.get_wmi_network_adapter_configuration(
|
|
320
|
+
interface_name=interface_name,
|
|
321
|
+
get_info_from_network_config=True)
|
|
322
|
+
|
|
323
|
+
global NETWORK_INTERFACE_SETTINGS
|
|
324
|
+
NETWORK_INTERFACE_SETTINGS = NetworkSettings(
|
|
325
|
+
name=adapter_info['name'],
|
|
326
|
+
description=adapter_info['description'],
|
|
327
|
+
interface_index=adapter_info['interface_index'],
|
|
328
|
+
is_dynamic=adapter_info['is_dynamic'],
|
|
329
|
+
ipv4s=adapter_info['ipv4s'],
|
|
330
|
+
ipv6s=adapter_info['ipv6s'],
|
|
331
|
+
ipv4_subnet_masks=adapter_info['ipv4_subnet_masks'],
|
|
332
|
+
ipv6_prefixes=adapter_info['ipv6_prefixes'],
|
|
333
|
+
default_gateways=adapter_info['default_gateways'],
|
|
334
|
+
dns_gateways=adapter_info['dns_gateways']
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Check if we need the localhost ips (12.0.0.1) or external local ips (192.168.0.100).
|
|
338
|
+
if config_static.MainConfig.is_localhost:
|
|
339
|
+
# Generate the list of localhost ips. We will start from 127.0.0.2 and end with 127.0.0.2(create_ips + 1)
|
|
340
|
+
for i in range(2, create_ips + 2):
|
|
341
|
+
engine_ips.append(f"127.0.0.{i}")
|
|
342
|
+
|
|
343
|
+
# If the current default DNS gateway ipv4 is inside the engine_ips, then we will remove it and add the next in line.
|
|
344
|
+
if config_static.MainConfig.default_localhost_dns_gateway_ipv4 in engine_ips:
|
|
345
|
+
engine_ips.remove(config_static.MainConfig.default_localhost_dns_gateway_ipv4)
|
|
346
|
+
engine_ips.append(f"127.0.0.{create_ips + 2}")
|
|
347
|
+
|
|
348
|
+
dns_listening_ipv4: str = config_static.MainConfig.default_localhost_dns_gateway_ipv4
|
|
349
|
+
else:
|
|
293
350
|
# Generate the IPs for the domains.
|
|
294
|
-
global
|
|
295
|
-
|
|
351
|
+
global IPS_TO_ASSIGN, MASKS_TO_ASSIGN
|
|
352
|
+
assignment_result: tuple | None = networks.add_virtual_ips_to_network_interface(
|
|
353
|
+
interface_name=interface_name,
|
|
296
354
|
number_of_ips=create_ips,
|
|
297
355
|
simulate_only=True)
|
|
298
356
|
|
|
299
|
-
|
|
357
|
+
if assignment_result is None:
|
|
358
|
+
return 1
|
|
359
|
+
|
|
360
|
+
IPS_TO_ASSIGN, MASKS_TO_ASSIGN = assignment_result
|
|
361
|
+
|
|
362
|
+
engine_ips += IPS_TO_ASSIGN
|
|
363
|
+
dns_listening_ipv4: str = NETWORK_INTERFACE_SETTINGS.ipv4s[0]
|
|
364
|
+
|
|
365
|
+
# Assign DNS listening address.
|
|
366
|
+
if config_static.DNSServer.listening_ipv4 != '':
|
|
367
|
+
config_static.DNSServer.listening_address = f"{config_static.DNSServer.listening_ipv4}:{str(config_static.DNSServer.listening_port)}"
|
|
368
|
+
else:
|
|
369
|
+
config_static.DNSServer.listening_address = f"{dns_listening_ipv4}:{str(config_static.DNSServer.listening_port)}"
|
|
300
370
|
|
|
301
371
|
# Add the ips to engines.
|
|
302
372
|
for engine in config_static.ENGINES_LIST:
|
|
@@ -309,23 +379,31 @@ def get_ipv4s_for_tcp_server():
|
|
|
309
379
|
if port in ports_to_create_ips_for:
|
|
310
380
|
engine.port_target_dict[port]['ip'] = engine_ips.pop(0)
|
|
311
381
|
|
|
382
|
+
return 0
|
|
312
383
|
|
|
313
|
-
|
|
384
|
+
|
|
385
|
+
def mitm_server(config_file_path: str, script_version: str) -> int:
|
|
314
386
|
on_exit.register_exit_handler(exit_cleanup, at_exit=False, kill_signal=False)
|
|
315
387
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
388
|
+
python_version: str = python_functions.get_python_version_string()
|
|
389
|
+
print_api.print_api(f"[*] Python Version: {python_version}")
|
|
390
|
+
|
|
391
|
+
compliance_message: str | None = python_functions.check_python_version_compliance(min_ver=(3,13), max_ver=(3,13,99))
|
|
392
|
+
if compliance_message is not None:
|
|
393
|
+
print_api.print_api(f"[!] {compliance_message}", error_type=True, color="red")
|
|
320
394
|
return 1
|
|
321
395
|
|
|
396
|
+
print_api.print_api("[*] Version Check PASSED.", color="green")
|
|
397
|
+
|
|
322
398
|
# Import the configuration file.
|
|
323
|
-
|
|
324
|
-
if
|
|
325
|
-
return
|
|
399
|
+
rc: int = config_static.load_config(config_file_path, print_kwargs=dict(stdout=False))
|
|
400
|
+
if rc != 0:
|
|
401
|
+
return rc
|
|
326
402
|
|
|
327
403
|
# Get the IPs that will be set for the adapter and fill the engine configuration with the IPs.
|
|
328
|
-
get_ipv4s_for_tcp_server()
|
|
404
|
+
rc: int = get_ipv4s_for_tcp_server()
|
|
405
|
+
if rc != 0:
|
|
406
|
+
return rc
|
|
329
407
|
|
|
330
408
|
global MITM_ERROR_LOGGER
|
|
331
409
|
MITM_ERROR_LOGGER = loggingw.ExceptionCsvLogger(
|
|
@@ -397,7 +475,7 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
397
475
|
is_ready_multiprocessing_event_list: list[multiprocessing.Event] = list()
|
|
398
476
|
|
|
399
477
|
# === Initialize TCP Server ====================================================================================
|
|
400
|
-
if config_static.TCPServer.
|
|
478
|
+
if config_static.TCPServer.is_enabled:
|
|
401
479
|
# Get the default network adapter configuration and set the one from config.
|
|
402
480
|
# We set the virtual IPs in the network adapter here, so the server multiprocessing processes can listen on them.
|
|
403
481
|
setting_result: int = _add_virtual_ips_set_default_dns_gateway(system_logger)
|
|
@@ -489,6 +567,8 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
489
567
|
exceptions_logger_queue=EXCEPTIONS_CSV_LOGGER_QUEUE,
|
|
490
568
|
forwarding_dns_service_ipv4_list___only_for_localhost=[config_static.DNSServer.forwarding_dns_service_ipv4],
|
|
491
569
|
skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST,
|
|
570
|
+
enable_sslkeylogfile_env_to_client_ssl_context=config_static.Certificates.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
571
|
+
sslkeylog_file_path=config_static.Certificates.sslkeylog_file_path,
|
|
492
572
|
print_kwargs=dict(stdout=False)
|
|
493
573
|
)
|
|
494
574
|
|
|
@@ -521,7 +601,7 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
521
601
|
# === EOF Initialize TCP Server ====================================================================================
|
|
522
602
|
|
|
523
603
|
# === Initialize DNS module ========================================================================================
|
|
524
|
-
if config_static.DNSServer.
|
|
604
|
+
if config_static.DNSServer.is_enabled:
|
|
525
605
|
# noinspection PyTypeHints
|
|
526
606
|
is_dns_process_ready: multiprocessing.Event = multiprocessing.Event()
|
|
527
607
|
|
|
@@ -536,7 +616,7 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
536
616
|
resolve_regular_pass_thru=config_static.DNSServer.resolve_regular_pass_thru,
|
|
537
617
|
resolve_all_domains_to_ipv4=(
|
|
538
618
|
config_static.DNSServer.resolve_all_domains_to_ipv4_enable, config_static.DNSServer.target_ipv4),
|
|
539
|
-
offline_mode=config_static.MainConfig.
|
|
619
|
+
offline_mode=config_static.MainConfig.is_offline,
|
|
540
620
|
cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
|
|
541
621
|
logging_queue=NETWORK_LOGGER_QUEUE,
|
|
542
622
|
logger_name=network_logger_name,
|
|
@@ -558,7 +638,7 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
558
638
|
return 1
|
|
559
639
|
# === EOF Initialize DNS module ====================================================================================
|
|
560
640
|
|
|
561
|
-
if config_static.DNSServer.
|
|
641
|
+
if config_static.DNSServer.is_enabled or config_static.TCPServer.is_enabled:
|
|
562
642
|
print_api.print_api("The Server is Ready for Operation!", color="green", logger=system_logger)
|
|
563
643
|
print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue', logger=system_logger)
|
|
564
644
|
|
|
@@ -578,6 +658,8 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
578
658
|
|
|
579
659
|
time.sleep(1)
|
|
580
660
|
|
|
661
|
+
return 0
|
|
662
|
+
|
|
581
663
|
|
|
582
664
|
# noinspection PyTypeHints
|
|
583
665
|
def _create_tcp_server_process(
|
|
@@ -681,48 +763,59 @@ def _add_virtual_ips_set_default_dns_gateway(system_logger: logging.Logger) -> i
|
|
|
681
763
|
# This setting is needed only for the dns gateways configurations from the main config on localhost.
|
|
682
764
|
set_local_dns_gateway: bool = False
|
|
683
765
|
# Set the default gateway if specified.
|
|
684
|
-
if config_static.
|
|
685
|
-
dns_gateway_server_list = config_static.
|
|
766
|
+
if config_static.MainConfig.set_default_dns_gateway:
|
|
767
|
+
dns_gateway_server_list = config_static.MainConfig.set_default_dns_gateway
|
|
686
768
|
set_local_dns_gateway = True
|
|
687
|
-
elif config_static.
|
|
688
|
-
dns_gateway_server_list = [
|
|
769
|
+
elif config_static.MainConfig.set_default_dns_gateway_to_localhost:
|
|
770
|
+
dns_gateway_server_list = [config_static.MainConfig.default_localhost_dns_gateway_ipv4]
|
|
689
771
|
set_local_dns_gateway = True
|
|
690
|
-
elif config_static.
|
|
691
|
-
dns_gateway_server_list = [
|
|
772
|
+
elif config_static.MainConfig.set_default_dns_gateway_to_network_interface_ipv4:
|
|
773
|
+
dns_gateway_server_list = [NETWORK_INTERFACE_SETTINGS.ipv4s[0]]
|
|
692
774
|
set_local_dns_gateway = True
|
|
693
775
|
else:
|
|
694
776
|
dns_gateway_server_list = NETWORK_INTERFACE_SETTINGS.dns_gateways
|
|
695
777
|
|
|
696
|
-
if
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
778
|
+
if set_local_dns_gateway:
|
|
779
|
+
global IS_SET_DNS_GATEWAY
|
|
780
|
+
IS_SET_DNS_GATEWAY = True
|
|
781
|
+
|
|
782
|
+
# Get current network interface state.
|
|
783
|
+
global NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST
|
|
784
|
+
NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST = dns.get_default_dns_gateway()
|
|
785
|
+
|
|
786
|
+
# Set the DNS gateway to the specified one only if the DNS gateway is dynamic, or it is static but different
|
|
787
|
+
# from the one specified in the configuration file.
|
|
788
|
+
if (NETWORK_INTERFACE_IS_DYNAMIC or (not NETWORK_INTERFACE_IS_DYNAMIC and
|
|
789
|
+
NETWORK_INTERFACE_IPV4_ADDRESS_LIST != dns_gateway_server_list)):
|
|
790
|
+
try:
|
|
791
|
+
dns.set_interface_dns_gateway_static(
|
|
792
|
+
interface_name=NETWORK_INTERFACE_SETTINGS.name,
|
|
793
|
+
dns_servers=dns_gateway_server_list
|
|
794
|
+
)
|
|
795
|
+
except PermissionError as e:
|
|
796
|
+
print_api.print_api(e, error_type=True, color="red", logger=system_logger)
|
|
797
|
+
# Wait for the message to be printed and saved to file.
|
|
798
|
+
time.sleep(1)
|
|
799
|
+
# network_logger_queue_listener.stop()
|
|
800
|
+
return 1
|
|
801
|
+
|
|
802
|
+
if not config_static.MainConfig.is_localhost:
|
|
721
803
|
# Change the adapter settings and add the virtual IPs.
|
|
722
804
|
try:
|
|
723
|
-
networks.
|
|
724
|
-
|
|
725
|
-
|
|
805
|
+
networks.add_virtual_ips_to_network_interface(
|
|
806
|
+
interface_name=NETWORK_INTERFACE_SETTINGS.name,
|
|
807
|
+
virtual_ipv4s_to_add=IPS_TO_ASSIGN,
|
|
808
|
+
virtual_ipv4_masks_to_add=MASKS_TO_ASSIGN,
|
|
809
|
+
verbose=True,
|
|
810
|
+
logger=system_logger
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
for engine in config_static.ENGINES_LIST:
|
|
814
|
+
bindable_test_dict: dict = engine.domain_target_dict | engine.port_target_dict
|
|
815
|
+
for port_or_domain_name, ip_port_dict in bindable_test_dict.items():
|
|
816
|
+
print_api.print_api(f"Checking that virtual IP is bindable: {ip_port_dict['ip']}:{ip_port_dict['port']}", logger=system_logger)
|
|
817
|
+
networks.wait_for_ip_bindable_socket(ip_port_dict['ip'], port=int(ip_port_dict['port']), timeout=15)
|
|
818
|
+
print_api.print_api("BIND test successful for all virtual IPs.", logger=system_logger)
|
|
726
819
|
except (PermissionError, TimeoutError) as e:
|
|
727
820
|
print_api.print_api(e, error_type=True, color="red", logger=system_logger)
|
|
728
821
|
# Wait for the message to be printed and saved to file.
|
|
@@ -760,6 +853,29 @@ def _loop_at_midnight_recs_archive(network_logger_name):
|
|
|
760
853
|
|
|
761
854
|
|
|
762
855
|
def mitm_server_main(config_file_path: str, script_version: str):
|
|
856
|
+
# This is for Linux and macOS.
|
|
857
|
+
signal.signal(signal.SIGTERM, _graceful_shutdown)
|
|
858
|
+
signal.signal(signal.SIGINT, _graceful_shutdown)
|
|
859
|
+
# This is for Windows.
|
|
860
|
+
"""
|
|
861
|
+
Example:
|
|
862
|
+
script = (Path(__file__).resolve().parent / "ServerTCPWithDNS.py").resolve()
|
|
863
|
+
p = subprocess.Popen(
|
|
864
|
+
[sys.executable, "-u", str(script)],
|
|
865
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
|
|
866
|
+
# inherit console; do NOT use CREATE_NEW_CONSOLE
|
|
867
|
+
)
|
|
868
|
+
time.sleep(30)
|
|
869
|
+
p.send_signal(signal.CTRL_BREAK_EVENT)
|
|
870
|
+
try:
|
|
871
|
+
p.wait(timeout=5)
|
|
872
|
+
except subprocess.TimeoutExpired:
|
|
873
|
+
print("Graceful interrupt timed out; terminating")
|
|
874
|
+
p.terminate()
|
|
875
|
+
p.wait()
|
|
876
|
+
"""
|
|
877
|
+
signal.signal(signal.SIGBREAK, _graceful_shutdown)
|
|
878
|
+
|
|
763
879
|
try:
|
|
764
880
|
# Main function should return integer with error code, 0 is successful.
|
|
765
881
|
return mitm_server(config_file_path, script_version)
|
atomicshop/mitm/recs_files.py
CHANGED
|
@@ -2,8 +2,9 @@ import datetime
|
|
|
2
2
|
import os
|
|
3
3
|
import multiprocessing
|
|
4
4
|
import logging
|
|
5
|
+
import zipfile
|
|
6
|
+
import shutil
|
|
5
7
|
|
|
6
|
-
from ..archiver import zips
|
|
7
8
|
from .. import filesystem, print_api
|
|
8
9
|
from .. wrappers.loggingw import consts, loggingw
|
|
9
10
|
|
|
@@ -13,6 +14,40 @@ REC_FILE_DATE_TIME_FORMAT: str = f'{consts.DEFAULT_ROTATING_SUFFIXES_FROM_WHEN["
|
|
|
13
14
|
REC_FILE_DATE_FORMAT: str = REC_FILE_DATE_TIME_FORMAT.split('_')[0]
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def archive(
|
|
18
|
+
directory_path: str,
|
|
19
|
+
include_root_directory: bool = True,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Function archives the directory.
|
|
23
|
+
:param directory_path: string, full path to the directory.
|
|
24
|
+
:param include_root_directory: boolean, default is 'True'.
|
|
25
|
+
'True': The root directory will be included in the archive.
|
|
26
|
+
'False': The root directory will not be included in the archive.
|
|
27
|
+
True is usually the case in most archiving utilities.
|
|
28
|
+
:return: string, full path to the archived file.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# This is commonly used and supported by most ZIP utilities.
|
|
32
|
+
compression_method = zipfile.ZIP_DEFLATED
|
|
33
|
+
|
|
34
|
+
archive_path: str = directory_path + '.zip'
|
|
35
|
+
with zipfile.ZipFile(archive_path, 'w', compression_method) as zip_object:
|
|
36
|
+
for root, _, files in os.walk(directory_path):
|
|
37
|
+
for file in files:
|
|
38
|
+
file_path = os.path.join(root, file)
|
|
39
|
+
|
|
40
|
+
# If including the root directory, use the relative path from the parent directory of the root
|
|
41
|
+
if include_root_directory:
|
|
42
|
+
arcname = os.path.relpath(file_path, os.path.dirname(directory_path))
|
|
43
|
+
else:
|
|
44
|
+
arcname = os.path.relpath(file_path, directory_path)
|
|
45
|
+
|
|
46
|
+
zip_object.write(file_path, arcname)
|
|
47
|
+
|
|
48
|
+
return archive_path
|
|
49
|
+
|
|
50
|
+
|
|
16
51
|
def recs_archiver(
|
|
17
52
|
recs_directory: str,
|
|
18
53
|
logging_queue: multiprocessing.Queue,
|
|
@@ -69,6 +104,8 @@ def recs_archiver(
|
|
|
69
104
|
try:
|
|
70
105
|
archived_files: list = list()
|
|
71
106
|
for directory_path, all_recs_files in file_list_per_directory:
|
|
107
|
+
print_api.print_api(f"Archiving recs files in directory: {directory_path.path}",
|
|
108
|
+
logger=rec_packer_logger_with_queue_handler, color='blue')
|
|
72
109
|
for recs_atomic_path in all_recs_files:
|
|
73
110
|
# We don't need to archive today's files.
|
|
74
111
|
if today_date_string == recs_atomic_path.datetime_string:
|
|
@@ -82,10 +119,29 @@ def recs_archiver(
|
|
|
82
119
|
# Archive directories.
|
|
83
120
|
archive_directories: list = filesystem.get_paths_from_directory(
|
|
84
121
|
directory_path.path, get_directory=True, recursive=False)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
|
|
123
|
+
if not archive_directories:
|
|
124
|
+
print_api.print_api(
|
|
125
|
+
f"No directories to archive in: {directory_path.path}",
|
|
126
|
+
color='blue',
|
|
127
|
+
logger=rec_packer_logger_with_queue_handler
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
total_archived_files: int = 0
|
|
131
|
+
for archive_directory in archive_directories:
|
|
132
|
+
files_to_archive: list = filesystem.get_paths_from_directory(
|
|
133
|
+
directory_path=archive_directory.path, get_file=True, recursive=False)
|
|
134
|
+
total_archived_files += len(files_to_archive)
|
|
135
|
+
archived_file: str = archive(archive_directory.path, include_root_directory=True)
|
|
136
|
+
# Remove the original directory after archiving.
|
|
137
|
+
shutil.rmtree(archive_directory.path, ignore_errors=True)
|
|
138
|
+
archived_files.append(archived_file)
|
|
139
|
+
|
|
140
|
+
print_api.print_api(
|
|
141
|
+
f'Archived: 'f'Directories: {len(archive_directories)} | '
|
|
142
|
+
f'Total Files: {total_archived_files} | In: {directory_path.path}',
|
|
143
|
+
logger=rec_packer_logger_with_queue_handler, color='blue')
|
|
144
|
+
print_api.print_api(f'Archived files: {archived_files}', logger=rec_packer_logger_with_queue_handler)
|
|
89
145
|
|
|
90
146
|
finalize_output_queue.put(None)
|
|
91
147
|
|