atomicshop 3.3.1__py3-none-any.whl → 3.3.3__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.3.1'
4
+ __version__ = '3.3.3'
@@ -36,6 +36,8 @@ class ArgparseWrapper:
36
36
  # formatter_class=RawTextHelpFormatter: shows raw text and not the default argparse text parsing.
37
37
  self.parser = argparse.ArgumentParser(description=self.description_full,
38
38
  usage=self.usage_variable,
39
+ # usage=argparse.SUPPRESS # Remove usage line from the beginning.
40
+ # add_help=False, # Remove help line from the end.
39
41
  formatter_class=RawTextHelpFormatter)
40
42
 
41
43
  def add_arguments(self) -> None:
@@ -68,7 +68,8 @@ def is_certificate_in_store(
68
68
  by_cert_issuer: bool = True,
69
69
  by_cert_thumbprint: bool = True,
70
70
  issuer_name: str = None,
71
- store_location: str = "ROOT"
71
+ store_location: str = "ROOT",
72
+ print_kwargs: dict = None
72
73
  ) -> tuple[bool, list]:
73
74
  """
74
75
  The function will check if the CA certificate is installed in the Windows certificate Trusted Root store.
@@ -89,6 +90,7 @@ def is_certificate_in_store(
89
90
  certificates with the same issuer name.
90
91
  :param issuer_name: string, issuer name to search for. You can search by certificate or by issuer name.
91
92
  :param store_location: string, store location to search in. Default is "ROOT".
93
+ :param print_kwargs: dict, print_api kwargs.
92
94
  :return: tuple(bool - True if certificate is installed and False if not, list of certificates found)
93
95
  """
94
96
 
@@ -102,7 +104,7 @@ def is_certificate_in_store(
102
104
 
103
105
  if certificate:
104
106
  # Make sure the certificate is x509.Certificate object.
105
- certificate_x509 = cryptographyw.convert_object_to_x509(certificate)
107
+ certificate_x509 = cryptographyw.convert_object_to_x509(certificate, print_kwargs=print_kwargs)
106
108
  # Get the certificate thumbprint.
107
109
  provided_thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(certificate_x509)
108
110
  provided_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(certificate_x509)
@@ -117,7 +119,12 @@ def is_certificate_in_store(
117
119
  result_found_list: list = []
118
120
  found: bool = False
119
121
  for cert, encoding, trust in ssl.enum_certificates(store_location):
122
+ # Some certificates in the store can have zero or negative serial number.
123
+ # We will skip them, since they're deprecated by the cryptography library.
120
124
  store_certificate = cryptographyw.convert_object_to_x509(cert)
125
+ if not store_certificate:
126
+ continue
127
+
121
128
  store_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(store_certificate)
122
129
  store_thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(store_certificate)
123
130
 
atomicshop/filesystem.py CHANGED
@@ -288,7 +288,11 @@ def remove_file(file_path: str, **kwargs) -> bool:
288
288
  return False
289
289
 
290
290
 
291
- def remove_directory(directory_path: str, force_readonly: bool = False, print_kwargs: dict = None) -> bool:
291
+ def remove_directory(
292
+ directory_path: str,
293
+ force_readonly: bool = False,
294
+ print_kwargs: dict = None
295
+ ) -> bool:
292
296
  """
293
297
  Remove directory if it exists.
294
298
 
@@ -93,7 +93,10 @@ def import_engines_configs(print_kwargs: dict) -> int:
93
93
  for engine_config_path in engine_config_path_list:
94
94
  # Initialize engine.
95
95
  current_module: initialize_engines.ModuleCategory = initialize_engines.ModuleCategory(config_static.MainConfig.SCRIPT_DIRECTORY)
96
- current_module.fill_engine_fields_from_config(engine_config_path.path, print_kwargs=print_kwargs or {})
96
+ result_code, error = current_module.fill_engine_fields_from_config(engine_config_path.path, print_kwargs=print_kwargs or {})
97
+ if result_code != 0:
98
+ print_api(f"Error reading engine config file: {engine_config_path.path}\n{error}", color='red')
99
+ return result_code
97
100
  result_code, error = current_module.initialize_engine(print_kwargs=print_kwargs or {})
98
101
  if result_code != 0:
99
102
  print_api(f"Error initializing engine from directory: {Path(engine_config_path.path).parent}\n{error}", color='red')
@@ -45,7 +45,7 @@ class ModuleCategory:
45
45
  self,
46
46
  engine_config_file_path: str,
47
47
  print_kwargs: dict = None
48
- ):
48
+ ) -> tuple[int, str]:
49
49
  # Read the configuration file of the engine.
50
50
  configuration_data = tomls.read_toml_file(engine_config_file_path, **(print_kwargs or {}))
51
51
 
@@ -82,7 +82,11 @@ class ModuleCategory:
82
82
 
83
83
  for domain_index, domain_port_string in enumerate(self.domain_list):
84
84
  # Splitting the domain and port
85
- domain, port = domain_port_string.split(':')
85
+ if ':' in domain_port_string:
86
+ domain, port = domain_port_string.split(':')
87
+ else:
88
+ error_string: str = f"No [domain:port] pair found in: {domain_port_string}"
89
+ return 1, error_string
86
90
 
87
91
  self.domain_target_dict[domain] = {'ip': None, 'port': port}
88
92
 
@@ -96,6 +100,8 @@ class ModuleCategory:
96
100
  for subdomain, file_name in self.mtls.items():
97
101
  self.mtls[subdomain] = f'{engine_directory_path}{os.sep}{file_name}'
98
102
 
103
+ return 0, ''
104
+
99
105
  def initialize_engine(
100
106
  self,
101
107
  reference_general: bool = False,
@@ -320,7 +320,7 @@ def mitm_server(config_file_path: str, script_version: str):
320
320
  return 1
321
321
 
322
322
  # Import the configuration file.
323
- result = config_static.load_config(config_file_path)
323
+ result = config_static.load_config(config_file_path, print_kwargs=dict(stdout=False))
324
324
  if result != 0:
325
325
  return result
326
326
 
@@ -392,54 +392,17 @@ def mitm_server(config_file_path: str, script_version: str):
392
392
  # Logging Startup information.
393
393
  startup_output(system_logger, script_version)
394
394
 
395
- print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue')
396
-
397
395
  multiprocess_list: list[multiprocessing.Process] = list()
398
396
  # noinspection PyTypeHints
399
397
  is_ready_multiprocessing_event_list: list[multiprocessing.Event] = list()
400
398
 
401
- # === Initialize DNS module ====================================================================================
402
- if config_static.DNSServer.enable:
403
- is_dns_process_ready: multiprocessing.Event = multiprocessing.Event()
404
- is_ready_multiprocessing_event_list.append(is_dns_process_ready)
405
-
406
- dns_server_kwargs: dict = dict(
407
- listening_address=config_static.DNSServer.listening_address,
408
- log_directory_path=config_static.LogRec.logs_path,
409
- backupCount_log_files_x_days=config_static.LogRec.store_logs_for_x_days,
410
- forwarding_dns_service_ipv4=config_static.DNSServer.forwarding_dns_service_ipv4,
411
- forwarding_dns_service_port=config_static.DNSServer.forwarding_dns_service_port,
412
- resolve_by_engine=(
413
- config_static.DNSServer.resolve_by_engine, config_static.ENGINES_LIST),
414
- resolve_regular_pass_thru=config_static.DNSServer.resolve_regular_pass_thru,
415
- resolve_all_domains_to_ipv4=(
416
- config_static.DNSServer.resolve_all_domains_to_ipv4_enable, config_static.DNSServer.target_ipv4),
417
- offline_mode=config_static.MainConfig.offline,
418
- cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
419
- logging_queue=NETWORK_LOGGER_QUEUE,
420
- logger_name=network_logger_name,
421
- is_ready_multiprocessing=is_dns_process_ready
422
- )
423
-
424
- dns_process = multiprocessing.Process(
425
- target=dns_server.start_dns_server_multiprocessing_worker,
426
- kwargs=dns_server_kwargs,
427
- name="dns_server",
428
- daemon=True
429
- )
430
- dns_process.start()
431
-
432
- multiprocess_list.append(dns_process)
433
-
434
-
435
- # === EOF Initialize DNS module ================================================================================
436
399
  # === Initialize TCP Server ====================================================================================
437
400
  if config_static.TCPServer.enable:
438
401
  # Get the default network adapter configuration and set the one from config.
439
402
  # We set the virtual IPs in the network adapter here, so the server multiprocessing processes can listen on them.
440
403
  setting_result: int = _add_virtual_ips_set_default_dns_gateway(system_logger)
441
404
  if setting_result != 0:
442
- print_api.print_api("Failed to set the default DNS gateway.", error_type=True, color="red",
405
+ print_api.print_api("Failed to set the default DNS gateway OR Virtual IPs.", error_type=True, color="red",
443
406
  logger=system_logger)
444
407
  # Wait for the message to be printed and saved to file.
445
408
  time.sleep(1)
@@ -525,9 +488,11 @@ def mitm_server(config_file_path: str, script_version: str):
525
488
  exceptions_logger_name=EXCEPTIONS_CSV_LOGGER_NAME,
526
489
  exceptions_logger_queue=EXCEPTIONS_CSV_LOGGER_QUEUE,
527
490
  forwarding_dns_service_ipv4_list___only_for_localhost=[config_static.DNSServer.forwarding_dns_service_ipv4],
528
- skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST
491
+ skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST,
492
+ print_kwargs=dict(stdout=False)
529
493
  )
530
494
 
495
+ # noinspection PyTypeHints
531
496
  is_tcp_process_ready: multiprocessing.Event = multiprocessing.Event()
532
497
  is_ready_multiprocessing_event_list.append(is_tcp_process_ready)
533
498
 
@@ -551,16 +516,52 @@ def mitm_server(config_file_path: str, script_version: str):
551
516
  recs_archiver_thread.start()
552
517
 
553
518
  # Check that all the multiprocesses are ready.
554
- for event in is_ready_multiprocessing_event_list:
555
- if not event.wait(timeout=30):
556
- print_api.print_api("One of the processes didn't start in time.", error_type=True, color="red",
557
- logger=system_logger)
558
- # Wait for the message to be printed and saved to file.
559
- time.sleep(1)
560
- return 1
519
+ if not _wait_for_events(is_ready_multiprocessing_event_list, timeout=30, system_logger=system_logger):
520
+ return 1
521
+ # === EOF Initialize TCP Server ====================================================================================
522
+
523
+ # === Initialize DNS module ========================================================================================
524
+ if config_static.DNSServer.enable:
525
+ # noinspection PyTypeHints
526
+ is_dns_process_ready: multiprocessing.Event = multiprocessing.Event()
527
+
528
+ dns_server_kwargs: dict = dict(
529
+ listening_address=config_static.DNSServer.listening_address,
530
+ log_directory_path=config_static.LogRec.logs_path,
531
+ backupCount_log_files_x_days=config_static.LogRec.store_logs_for_x_days,
532
+ forwarding_dns_service_ipv4=config_static.DNSServer.forwarding_dns_service_ipv4,
533
+ forwarding_dns_service_port=config_static.DNSServer.forwarding_dns_service_port,
534
+ resolve_by_engine=(
535
+ config_static.DNSServer.resolve_by_engine, config_static.ENGINES_LIST),
536
+ resolve_regular_pass_thru=config_static.DNSServer.resolve_regular_pass_thru,
537
+ resolve_all_domains_to_ipv4=(
538
+ config_static.DNSServer.resolve_all_domains_to_ipv4_enable, config_static.DNSServer.target_ipv4),
539
+ offline_mode=config_static.MainConfig.offline,
540
+ cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
541
+ logging_queue=NETWORK_LOGGER_QUEUE,
542
+ logger_name=network_logger_name,
543
+ is_ready_multiprocessing=is_dns_process_ready
544
+ )
545
+
546
+ dns_process = multiprocessing.Process(
547
+ target=dns_server.start_dns_server_multiprocessing_worker,
548
+ kwargs=dns_server_kwargs,
549
+ name="dns_server",
550
+ daemon=True
551
+ )
552
+ dns_process.start()
553
+
554
+ multiprocess_list.append(dns_process)
555
+
556
+ # Check that the multiprocess is ready.
557
+ if not _wait_for_events([is_dns_process_ready], timeout=30, system_logger=system_logger):
558
+ return 1
559
+ # === EOF Initialize DNS module ====================================================================================
561
560
 
562
561
  if config_static.DNSServer.enable or config_static.TCPServer.enable:
563
562
  print_api.print_api("The Server is Ready for Operation!", color="green", logger=system_logger)
563
+ print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue', logger=system_logger)
564
+
564
565
  # Get al the queue listener processes (basically this is not necessary, since they're 'daemons', but this is a good practice).
565
566
  multiprocess_list.extend(loggingw.get_listener_processes())
566
567
 
@@ -578,6 +579,7 @@ def mitm_server(config_file_path: str, script_version: str):
578
579
  time.sleep(1)
579
580
 
580
581
 
582
+ # noinspection PyTypeHints
581
583
  def _create_tcp_server_process(
582
584
  socket_wrapper_kwargs: dict,
583
585
  config_file_path: str,
@@ -616,8 +618,21 @@ def _create_tcp_server_process(
616
618
  # network_logger_queue_listener.stop()
617
619
  sys.exit(1)
618
620
 
619
- socket_wrapper_instance.start_listening_socket(
620
- callable_function=thread_worker_main, callable_args=(config_static,))
621
+ try:
622
+ socket_wrapper_instance.start_listening_socket(
623
+ callable_function=thread_worker_main, callable_args=(config_static,))
624
+ except OSError as e:
625
+ if e.winerror == 10022: # Invalid argument error on Windows.
626
+ message = (
627
+ str(f"{e}\n"
628
+ f"Check that the IP address and port are correct: {socket_wrapper_kwargs['ip_address']}:{socket_wrapper_kwargs['port']}\n"))
629
+ print_api.print_api(message, error_type=True, color="red", logger=system_logger, logger_method='critical')
630
+ # Wait for the message to be printed and saved to file.
631
+ time.sleep(1)
632
+ # network_logger_queue_listener.stop()
633
+ sys.exit(1)
634
+ else:
635
+ raise e
621
636
 
622
637
  # Notify that the TCP server is ready.
623
638
  is_tcp_process_ready.set()
@@ -629,6 +644,32 @@ def _create_tcp_server_process(
629
644
  sys.exit(0)
630
645
 
631
646
 
647
+ # noinspection PyTypeHints
648
+ def _wait_for_events(
649
+ events: list[multiprocessing.Event],
650
+ timeout: int = 30,
651
+ system_logger: logging.Logger = None
652
+ ) -> bool:
653
+ """
654
+ Wait for all events in the list to be set.
655
+
656
+ :param events: List of multiprocessing.Event objects.
657
+ :param timeout: Maximum time to wait for all events to be set.
658
+ :return: True if all events are set, False if timeout occurs.
659
+ """
660
+
661
+ # Check that all the multiprocesses are ready.
662
+ for event in events:
663
+ if not event.wait(timeout=timeout):
664
+ print_api.print_api("One of the processes didn't start in time.", error_type=True, color="red",
665
+ logger=system_logger)
666
+ # Wait for the message to be printed and saved to file.
667
+ time.sleep(1)
668
+ return False
669
+
670
+ return True
671
+
672
+
632
673
  def _add_virtual_ips_set_default_dns_gateway(system_logger: logging.Logger) -> int:
633
674
  """
634
675
  The function reads the current DNS gateway setting and sets the new one.
@@ -682,7 +723,7 @@ def _add_virtual_ips_set_default_dns_gateway(system_logger: logging.Logger) -> i
682
723
  networks.add_virtual_ips_to_default_adapter_by_current_setting(
683
724
  virtual_ipv4s_to_add=IPS_TO_ASSIGN, virtual_ipv4_masks_to_add=MASKS_TO_ASSIGN,
684
725
  dns_gateways=dns_gateway_server_list)
685
- except PermissionError as e:
726
+ except (PermissionError, TimeoutError) as e:
686
727
  print_api.print_api(e, error_type=True, color="red", logger=system_logger)
687
728
  # Wait for the message to be printed and saved to file.
688
729
  time.sleep(1)
atomicshop/networks.py CHANGED
@@ -2,6 +2,7 @@ import socket
2
2
  import time
3
3
  from typing import Union
4
4
  import os
5
+ import psutil
5
6
 
6
7
  from icmplib import ping
7
8
  from icmplib.models import Host
@@ -10,6 +11,7 @@ from win32com.client import CDispatch
10
11
  from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers, msft_netipaddress
11
12
  from .wrappers.ctyping import setup_device
12
13
  from .wrappers.winregw import winreg_network
14
+ from .wrappers.psutilw import psutil_networks
13
15
 
14
16
 
15
17
  MICROSOFT_LOOPBACK_DEVICE_NAME: str = 'Microsoft KM-TEST Loopback Adapter'
@@ -42,7 +44,7 @@ def get_default_internet_ipv4_by_connect(target: str = "8.8.8.8") -> str:
42
44
  return s.getsockname()[0] # local address of that route
43
45
 
44
46
 
45
- def get_default_internet_interface_name() -> str:
47
+ def get_hostname() -> str:
46
48
  """
47
49
  Get the default network interface name that is being used for internet.
48
50
  :return: string, default network interface name.
@@ -51,6 +53,61 @@ def get_default_internet_interface_name() -> str:
51
53
  return socket.gethostname()
52
54
 
53
55
 
56
+ def get_default_internet_interface_name() -> str | None:
57
+ """
58
+ Get the default network interface name that is being used for internet.
59
+ :return: string, default network interface name.
60
+ """
61
+
62
+ interface_dict: dict = psutil_networks.get_default_connection_name()
63
+ if not interface_dict:
64
+ result = None
65
+ else:
66
+ # Get the first interface name from the dictionary.
67
+ result = next(iter(interface_dict.keys()), None)
68
+
69
+ return result
70
+
71
+
72
+ def get_interface_ips(
73
+ interface_name: str = None,
74
+ ipv4: bool = True,
75
+ ipv6: bool = True,
76
+ localhost: bool = True,
77
+ default_interface: bool = False
78
+ ):
79
+ if not ipv4 and not ipv6:
80
+ raise ValueError("At least one of ipv4 or ipv6 must be True.")
81
+ if default_interface and interface_name:
82
+ raise ValueError("You can't specify both default_interface and interface_name.")
83
+
84
+ if default_interface:
85
+ # Get the default interface name.
86
+ interface_name = get_default_internet_interface_name()
87
+
88
+ physical_ip_types: list[str] = []
89
+ if ipv4:
90
+ physical_ip_types.append("AF_INET") # IPv4
91
+ if ipv6:
92
+ physical_ip_types.append("AF_INET6") # IPv6
93
+
94
+ interfaces: dict = psutil.net_if_addrs()
95
+
96
+ ips = []
97
+ for name, addresses in interfaces.items():
98
+ if interface_name and interface_name != name:
99
+ continue
100
+
101
+ for address in addresses:
102
+ if address.family.name in physical_ip_types:
103
+ if not localhost and (address.address.startswith("127.") or address.address.startswith("::1")):
104
+ # Skip localhost addresses if localhost is True.
105
+ continue
106
+
107
+ ips.append(address.address)
108
+ return ips
109
+
110
+
54
111
  def get_microsoft_loopback_device_network_configuration(
55
112
  wmi_instance: CDispatch = None,
56
113
  timeout: int = 1,
@@ -351,6 +408,8 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
351
408
  availability_wait_seconds: int = 15,
352
409
  simulate_only: bool = False,
353
410
  locator: CDispatch = None,
411
+ wait_until_applied: bool = True,
412
+ wait_until_applied_seconds: int = 15
354
413
  ) -> tuple[list[str], list[str], list[str], list[str]]:
355
414
  """
356
415
  Add virtual IP addresses to the default network adapter.
@@ -381,6 +440,14 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
381
440
  :param simulate_only: bool, if True, the function will only prepare the ip addresses and return them without changing anything.
382
441
  :param locator: CDispatch, WMI locator object. If not specified, it will be created.
383
442
 
443
+ :param wait_until_applied: bool, if True, the function will wait until the IP addresses are applied.
444
+ By default, while WMI command is executed, there is no indication if the addresses were finished applying or not.
445
+ If you have 15+ addresses, it can take a while to apply them.
446
+ :param wait_until_applied_seconds: int, seconds to wait for the IP addresses to be applied.
447
+ This is different from availability_wait_seconds, which is the time to wait for the adapter to be available
448
+ after setting the IP addresses. This is the time to wait for the IP addresses to be
449
+ applied after setting them. If the IP addresses are not applied in this time, a TimeoutError will be raised.
450
+
384
451
  :return: tuple of lists, (current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign)
385
452
  """
386
453
 
@@ -462,4 +529,14 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
462
529
  # print("[!] No new IPs to assign.")
463
530
  pass
464
531
 
532
+ if wait_until_applied:
533
+ # Wait until the IP addresses are applied.
534
+ for _ in range(wait_until_applied_seconds):
535
+ current_ips = get_interface_ips(ipv4=True, ipv6=False, localhost=False, default_interface=True)
536
+ if set(current_ips) == set(ips):
537
+ break
538
+ time.sleep(1)
539
+ else:
540
+ raise TimeoutError("Timeout while waiting for the IP addresses to be applied.")
541
+
465
542
  return current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign
@@ -198,6 +198,7 @@ class CertificateAuthority(object):
198
198
 
199
199
  cert.set_issuer(cert.get_subject())
200
200
  cert.set_pubkey(key)
201
+
201
202
  cert.add_extensions([
202
203
  crypto.X509Extension(b"basicConstraints",
203
204
  True,
@@ -1,5 +1,6 @@
1
1
  from typing import Union
2
2
  import os
3
+ import warnings
3
4
 
4
5
  from ..print_api import print_api
5
6
  from ..file_io import file_io
@@ -9,6 +10,7 @@ from cryptography.x509 import Certificate
9
10
  from cryptography.hazmat.primitives.asymmetric import rsa
10
11
  from cryptography.hazmat.primitives import serialization
11
12
  from cryptography.hazmat.primitives import hashes
13
+ from cryptography.utils import CryptographyDeprecationWarning
12
14
 
13
15
 
14
16
  """
@@ -22,7 +24,10 @@ OID_TO_BUILDER_CLASS_EXTENSION_NAME: dict = {
22
24
  }
23
25
 
24
26
 
25
- def convert_object_to_x509(certificate):
27
+ def convert_object_to_x509(
28
+ certificate: Union[str, bytes, Certificate],
29
+ print_kwargs: dict = None
30
+ ) -> x509.Certificate | None:
26
31
  """Convert certificate to x509 object.
27
32
 
28
33
  :param certificate: any object that can be converted to x509 object.
@@ -32,7 +37,8 @@ def convert_object_to_x509(certificate):
32
37
  string that is PEM certificate will be converted to bytes, then x509.Certificate
33
38
  bytes of PEM or DER will be converted to x509.Certificate.
34
39
  x509.Certificate will be returned as is.
35
- :return: certificate in x509 object of 'cryptography' module.
40
+ :param print_kwargs: dict, additional arguments to pass to the print function.
41
+ :return: certificate in x509 object of 'cryptography' module. Or None if the certificate is not valid.
36
42
  """
37
43
 
38
44
  # Check if 'certificate' is a string and a path.
@@ -40,7 +46,7 @@ def convert_object_to_x509(certificate):
40
46
  if not os.path.isfile(certificate):
41
47
  raise FileNotFoundError(f'File not found: {certificate}')
42
48
  # Import the certificate from the path.
43
- certificate = file_io.read_file(certificate, file_mode='rb')
49
+ certificate = file_io.read_file(certificate, file_mode='rb', **(print_kwargs or {}))
44
50
 
45
51
  # Check if 'certificate' is a bytes object and PEM format.
46
52
  # We're checking if it starts with '-----BEGIN ' since the pem certificate can include PRIVATE KEY and will be
@@ -82,14 +88,27 @@ def convert_pem_to_x509_object(certificate: Union[str, bytes]) -> x509.Certifica
82
88
  return x509.load_pem_x509_certificate(certificate)
83
89
 
84
90
 
85
- def convert_der_to_x509_object(certificate: bytes) -> x509.Certificate:
91
+ def convert_der_to_x509_object(certificate: bytes) -> x509.Certificate | None:
86
92
  """Convert DER certificate from socket to x509 object.
87
93
 
88
94
  :param certificate: bytes, certificate to convert.
89
95
  :return: certificate in x509 object of 'cryptography' module.
90
96
  """
91
97
 
92
- return x509.load_der_x509_certificate(certificate)
98
+ # Some certificates in the store can have zero or negative serial number.
99
+ # We will skip them, since they're deprecated by the cryptography library.
100
+
101
+ try:
102
+ with warnings.catch_warnings():
103
+ # Turn the deprecation warning into an exception we can trap.
104
+ warnings.filterwarnings("error", category=CryptographyDeprecationWarning)
105
+ converted_certificate = x509.load_der_x509_certificate(certificate)
106
+ except (CryptographyDeprecationWarning, ValueError):
107
+ return None # serial was 0/negative → skip
108
+ if converted_certificate.serial_number <= 0: # belt-and-braces
109
+ return None
110
+
111
+ return converted_certificate
93
112
 
94
113
 
95
114
  def convert_x509_object_to_pem_bytes(certificate) -> bytes:
@@ -1,6 +1,10 @@
1
1
  import sys
2
+ import os
2
3
  import subprocess
3
4
  import getpass
5
+ import tempfile
6
+ import textwrap
7
+ from pathlib import Path
4
8
 
5
9
  from ... import process, filesystem
6
10
  from ...permissions import permissions, ubuntu_permissions
@@ -8,6 +12,11 @@ from ...print_api import print_api
8
12
  from .. import ubuntu_terminal
9
13
 
10
14
 
15
+ PREPARATION_OUTPUT_DIR: str = str(Path(__file__).parent / "offline-bundle")
16
+ PREPARATION_OUTPUT_ZIP: str = f"{PREPARATION_OUTPUT_DIR}.zip"
17
+ GET_DOCKER_URL: str = "https://get.docker.com"
18
+
19
+
11
20
  def is_docker_installed():
12
21
  """
13
22
  The function will check if docker is installed.
@@ -109,6 +118,7 @@ def install_docker_ubuntu(
109
118
  # The script will also install docker-compose and docker-buildx.
110
119
  # process.execute_script('curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh', shell=True)
111
120
  process.execute_script('curl -fsSL https://get.docker.com | sh', shell=True)
121
+ # subprocess.run("curl -fsSL https://get.docker.com | sh", shell=True, check=True)
112
122
  # process.execute_script('curl -fsSL https://get.docker.com -o get-docker.sh', shell=True)
113
123
  # process.execute_script('sh get-docker.sh', shell=True)
114
124
  # filesystem.remove_file('get-docker.sh')
@@ -210,3 +220,230 @@ def install_docker_ubuntu(
210
220
  print_api('Docker installation failed.', color='red')
211
221
  print_api('Please check the logs above for more information.', color='red')
212
222
  return 1
223
+
224
+
225
+ def prepare_offline_installation_bundle():
226
+ # The Bash script in a single triple-quoted string - this is to easier copy-paste it if needed to run directly.
227
+ bash_script = textwrap.dedent(r"""#!/usr/bin/env bash
228
+ #
229
+ # Build an offline-install bundle for Docker Engine on Ubuntu 24.04 LTS.
230
+ # The package list is auto-discovered from `get.docker.com --dry-run`.
231
+ #
232
+ # sudo ./prepare_docker_offline.sh [/path/to/output_dir]
233
+ #
234
+ set -Eeuo pipefail
235
+
236
+ ################################################################################
237
+ # CLI PARAMETERS
238
+ # $1 → OUTDIR (already supported: where to build the bundle)
239
+ # $2 → GET_DOCKER_URL (defaults to https://get.docker.com)
240
+ # $3 → OUTPUT_ZIP (defaults to "$OUTDIR.zip")
241
+ ################################################################################
242
+ OUTDIR="${1:-"$PWD/offline-bundle"}"
243
+ GET_DOCKER_URL="${2:-https://get.docker.com}"
244
+ OUTPUT_ZIP="${3:-$OUTDIR.zip}"
245
+
246
+ die() { echo "ERROR: $*" >&2; exit 1; }
247
+ need_root() { [[ $EUID -eq 0 ]] || die "Run as root (use sudo)"; }
248
+ need_cmd() {
249
+ local cmd=$1
250
+ local pkg=${2:-$1} # default package == command
251
+ if ! command -v "$cmd" &>/dev/null; then
252
+ echo "[*] $cmd not found – installing $pkg ..."
253
+ apt-get update -qq
254
+ DEBIAN_FRONTEND=noninteractive \
255
+ apt-get install -y --no-install-recommends "$pkg" || \
256
+ die "Unable to install required package: $pkg"
257
+ fi
258
+ }
259
+
260
+ need_root
261
+ need_cmd curl
262
+
263
+ echo "[*] Discovering package list via get.docker.com --dry-run ..."
264
+ DRY_LOG=$(curl -fsSL "$GET_DOCKER_URL" | bash -s -- --dry-run)
265
+
266
+ echo "[*] Determining package list via --dry-run ..."
267
+ PKGS=$(printf '%s\n' "$DRY_LOG" | sed -n 's/.* install \(.*\) >\/dev\/null.*/\1/p')
268
+
269
+ if ! grep -q '\S' <<< "$PKGS"; then
270
+ echo "No packages detected in dry-run output – aborting." >&2
271
+ exit 1
272
+ fi
273
+
274
+ echo "[*] Install Docker before preparing the offline bundle."
275
+ curl -fsSL "$GET_DOCKER_URL" | sh
276
+
277
+ mkdir -p "$OUTDIR"/packages
278
+ echo "[*] Output directory: $OUTDIR"
279
+
280
+ echo "Packages to install:"
281
+ echo "$PKGS"
282
+
283
+ echo "[*] Downloading packages and all dependencies …"
284
+ apt-get update -qq
285
+ apt-get clean
286
+ mkdir -p /var/cache/apt/archives/partial
287
+ apt-get -y --download-only --reinstall install $PKGS
288
+ cp -v /var/cache/apt/archives/*.deb "$OUTDIR/packages/"
289
+ echo "[*] $(ls "$OUTDIR/packages" | wc -l) .deb files written to packages/"
290
+
291
+ echo "[*] Building local Packages.gz index …"
292
+ pushd "$OUTDIR/packages" >/dev/null
293
+ for deb in *.deb; do
294
+ dpkg-deb -f "$deb" Package
295
+ done | awk '{printf "%s\tmisc\toptional\n",$1}' > override
296
+ apt-ftparchive packages . override | tee Packages | gzip -9c > Packages.gz
297
+ popd >/dev/null
298
+
299
+
300
+ echo ">> Checking for Docker ..."
301
+ command -v docker >/dev/null 2>&1 || { echo "Docker not found."; exit 1; }
302
+
303
+ # Pack final bundle
304
+ echo "[*] Creating a zip archive ..."
305
+ parent_dir=$(dirname "$OUTDIR")
306
+ base_name=$(basename "$OUTDIR")
307
+
308
+ # Create new shell, cd into the directory, and zip the contents. So that the zip file will not contain the full path.
309
+ (
310
+ cd "$parent_dir"
311
+ zip -rq "$OUTPUT_ZIP" "$base_name"
312
+ )
313
+
314
+ rm -rf "$OUTDIR"
315
+ echo "Docker offline bundle created at $OUTPUT_ZIP"
316
+ echo
317
+ echo "Copy the zip file and the offline installation python script to the target machine and execute."
318
+ """)
319
+
320
+ # Write it to a secure temporary file.
321
+ with tempfile.NamedTemporaryFile('w', delete=False, suffix='.sh') as f:
322
+ f.write(bash_script)
323
+ temp_path = f.name
324
+ os.chmod(temp_path, 0o755) # make it executable
325
+
326
+ cmd = [
327
+ "sudo", "bash", temp_path,
328
+ PREPARATION_OUTPUT_DIR,
329
+ GET_DOCKER_URL,
330
+ PREPARATION_OUTPUT_ZIP,
331
+ ]
332
+
333
+ # Run it and stream output live.
334
+ try:
335
+ subprocess.run(cmd, check=True)
336
+ finally:
337
+ # 5. Clean up the temp file unless you want to inspect it.
338
+ os.remove(temp_path)
339
+
340
+
341
+ def install_offline_installation_bundle():
342
+ bash_script = textwrap.dedent(r"""#!/usr/bin/env bash
343
+ # Offline installer for the Docker bundle produced by prepare_docker_offline.sh
344
+ set -euo pipefail
345
+
346
+ die() { echo "ERROR: $*" >&2; exit 1; }
347
+ need_root() { [[ $EUID -eq 0 ]] || die "Run as root (use sudo)"; }
348
+
349
+ need_root
350
+
351
+ # ------------------------------------------------------------------------------
352
+ # Paths
353
+ # ------------------------------------------------------------------------------
354
+ BUNDLE_ZIP="${1:-"$PWD/offline-bundle.zip"}"
355
+
356
+ BUNDLE_DIR="${BUNDLE_ZIP%.zip}" # remove .zip suffix
357
+ REPO_DIR="$BUNDLE_DIR/packages" # contains *.deb + Packages
358
+ OFFLINE_LIST="/etc/apt/sources.list.d/docker-offline.list"
359
+
360
+ # Extract zip archive if it exists
361
+ if [[ -f "$BUNDLE_ZIP" ]]; then
362
+ echo "[*] Extracting offline bundle from $BUNDLE_ZIP ..."
363
+ mkdir -p "$BUNDLE_DIR"
364
+ unzip -q "$BUNDLE_ZIP" -d "."
365
+ else
366
+ die "Bundle zip file '$BUNDLE_ZIP' not found. Provide a valid path."
367
+ fi
368
+
369
+ TEMP_PARTS="$(mktemp -d)" # empty dir ⇒ no extra lists
370
+
371
+ # ------------------------------------------------------------------------------
372
+ # Helper to clean up even if the script aborts
373
+ # ------------------------------------------------------------------------------
374
+ cleanup() {
375
+ sudo rm -f "$OFFLINE_LIST"
376
+ sudo rm -rf "$TEMP_PARTS"
377
+ }
378
+ trap cleanup EXIT
379
+
380
+ # ------------------------------------------------------------------------------
381
+ # 1. Add the local repository (trusted) as the *only* source we will use
382
+ # ------------------------------------------------------------------------------
383
+ echo "[*] Adding temporary APT source for the offline bundle …"
384
+ echo "deb [trusted=yes] file:$REPO_DIR ./" | sudo tee "$OFFLINE_LIST" >/dev/null
385
+
386
+ # Ensure plain index exists (APT always understands the un-compressed form)
387
+ if [[ ! -f "$REPO_DIR/Packages" && -f "$REPO_DIR/Packages.gz" ]]; then
388
+ gunzip -c "$REPO_DIR/Packages.gz" > "$REPO_DIR/Packages"
389
+ fi
390
+
391
+ # ------------------------------------------------------------------------------
392
+ # 2. Update metadata – but ONLY from our offline list
393
+ # ------------------------------------------------------------------------------
394
+ echo "[*] Updating APT metadata – offline only …"
395
+ sudo apt-get -o Dir::Etc::sourcelist="$OFFLINE_LIST" \
396
+ -o Dir::Etc::sourceparts="$TEMP_PARTS" \
397
+ -o APT::Get::List-Cleanup="0" \
398
+ update -qq
399
+
400
+ # ------------------------------------------------------------------------------
401
+ # 3. Figure out which packages are inside the bundle
402
+ # ------------------------------------------------------------------------------
403
+ PKGS=$(awk '/^Package: /{print $2}' "$REPO_DIR/Packages")
404
+
405
+ echo "[*] Installing:"
406
+ printf ' • %s\n' $PKGS
407
+
408
+ # ------------------------------------------------------------------------------
409
+ # 4. Install them, again restricting APT to the offline repo only
410
+ # ------------------------------------------------------------------------------
411
+ sudo apt-get -y \
412
+ -o Dir::Etc::sourcelist="$OFFLINE_LIST" \
413
+ -o Dir::Etc::sourceparts="$TEMP_PARTS" \
414
+ install $PKGS
415
+
416
+ echo "[✓] Docker installed completely offline!"
417
+
418
+ usage() {
419
+ echo "Usage: $0 <image-archive.tar.gz>"
420
+ exit 1
421
+ }
422
+
423
+ echo ">> Checking for Docker ..."
424
+ command -v docker >/dev/null 2>&1 || {
425
+ echo "Docker is not installed; install Docker and try again."
426
+ exit 1
427
+ }
428
+
429
+ echo "Removing extracted files..."
430
+ rm -rf "$BUNDLE_DIR"
431
+ """)
432
+
433
+ # Write it to a secure temporary file.
434
+ with tempfile.NamedTemporaryFile('w', delete=False, suffix='.sh') as f:
435
+ f.write(bash_script)
436
+ temp_path = f.name
437
+ os.chmod(temp_path, 0o755) # make it executable
438
+
439
+ cmd = [
440
+ "sudo", "bash", temp_path,
441
+ PREPARATION_OUTPUT_ZIP, # $1 BUNDLE_ZIP
442
+ ]
443
+
444
+ # 4. Run it and stream output live.
445
+ try:
446
+ subprocess.run(cmd, check=True)
447
+ finally:
448
+ # 5. Clean up the temp file unless you want to inspect it.
449
+ os.remove(temp_path)
@@ -8,7 +8,7 @@ from tempfile import gettempdir
8
8
  # noinspection PyPackageRequirements
9
9
  from playwright.sync_api import sync_playwright, Error
10
10
  # Stealth options for playwright. External.
11
- from playwright_stealth import stealth_sync
11
+ # from playwright_stealth import stealth_sync
12
12
 
13
13
  from ...keyboard_press import send_alt_tab
14
14
  from ... import filesystem, print_api
@@ -150,7 +150,7 @@ class PlaywrightEngine:
150
150
  self.page = self.browser.new_page()
151
151
 
152
152
  # Making playwright stealthier with less footprint of automation.
153
- stealth_sync(self.page)
153
+ # stealth_sync(self.page)
154
154
 
155
155
  def close_browser(self) -> None:
156
156
  self.page.close()
@@ -11,11 +11,9 @@ def parse_args():
11
11
  :return: Parsed arguments.
12
12
  """
13
13
  parser = argparse.ArgumentParser(description='Install PyCharm Community Edition.')
14
- parser.add_argument('-ic', '--install_community', action='store_true', required=True,
15
- help='Install PyCharm Community Edition with snapd.')
16
- parser.add_argument('--enable_sudo_execution', action='store_true',
17
- help='There is a problem when trying to run snapd installed Pycharm as sudo, need to enable '
18
- 'this.')
14
+ parser.add_argument(
15
+ '--enable_sudo_execution', action='store_true',
16
+ help='There is a problem when trying to run snapd installed Pycharm as sudo, need to enable this.')
19
17
 
20
18
  return parser.parse_args()
21
19
 
@@ -30,10 +28,9 @@ def install_main():
30
28
 
31
29
  args = parse_args()
32
30
 
33
- if args.install_community:
34
- process.execute_script('sudo snap install pycharm-community --classic', shell=True)
31
+ process.execute_script('sudo snap install pycharm-professional --classic', shell=True)
35
32
 
36
33
  if args.enable_sudo_execution:
37
34
  process.execute_script('xhost +SI:localuser:root', shell=True)
38
- print_api('Run the following command to start PyCharm as root: [sudo snap run pycharm-community]', color='blue')
35
+ print_api('Run the following command to start PyCharm as root: [sudo snap run pycharm-professional]', color='blue')
39
36
  return 0
@@ -1,4 +1,4 @@
1
- import random
1
+ from secrets import randbits
2
2
  from OpenSSL import crypto
3
3
 
4
4
  from .. import certificates
@@ -121,7 +121,14 @@ def generate_server_certificate_empty(
121
121
  """
122
122
 
123
123
  cert = crypto.X509()
124
- cert.set_serial_number(random.randint(0, 2 ** 64 - 1))
124
+
125
+ # RFC 5280: serial must be positive and non-zero
126
+ # 1 … 2⁶⁴-1
127
+ cert.set_serial_number(randbits(64))
128
+ current_serial = cert.get_serial_number()
129
+ if current_serial <= 0:
130
+ raise RuntimeError(f"Refusing to create a certificate with non-positive serial: {str(current_serial)}")
131
+
125
132
  cert.get_subject().CN = certname
126
133
 
127
134
  cert.set_version(2)
@@ -1,5 +1,6 @@
1
1
  import ssl
2
2
  from dataclasses import dataclass
3
+ from typing import Callable, Any
3
4
 
4
5
  from ..loggingw import loggingw
5
6
  from ...domains import get_domain_without_first_subdomain_if_no_subdomain_return_as_is
@@ -31,7 +32,7 @@ class SNISetup:
31
32
  default_server_certificate_name: str,
32
33
  default_server_certificate_directory: str,
33
34
  default_certificate_domain_list: list,
34
- sni_custom_callback_function: callable,
35
+ sni_custom_callback_function: Callable[..., Any],
35
36
  sni_use_default_callback_function: bool,
36
37
  sni_use_default_callback_function_extended: bool,
37
38
  sni_add_new_domains_to_default_server_certificate: bool,
@@ -54,7 +55,7 @@ class SNISetup:
54
55
  self.default_server_certificate_name = default_server_certificate_name
55
56
  self.default_server_certificate_directory = default_server_certificate_directory
56
57
  self.default_certificate_domain_list = default_certificate_domain_list
57
- self.sni_custom_callback_function: callable = sni_custom_callback_function
58
+ self.sni_custom_callback_function: Callable[..., Any] = sni_custom_callback_function
58
59
  self.sni_use_default_callback_function: bool = sni_use_default_callback_function
59
60
  self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
60
61
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -259,20 +260,24 @@ class SNIHandler:
259
260
  # If DNS server is enabled we'll get the domain from dns server.
260
261
  if self.domain_from_dns_server:
261
262
  self.sni_received_parameters.destination_name = self.domain_from_dns_server
262
- message = \
263
- f"SNI Handler: No SNI was passed, using domain from DNS Server: {self.domain_from_dns_server}"
263
+ print_api("SNI Passed: False", color="yellow", **(print_kwargs or {}))
264
+
265
+ message = f"SNI Handler: No SNI was passed, using domain from DNS Server: {self.domain_from_dns_server}"
264
266
  print_api(message, color="yellow", **(print_kwargs or {}))
265
267
  # If DNS server is disabled, the domain from dns server will be empty.
266
268
  else:
267
- message = f"SNI Handler: No SNI was passed, No domain passed from DNS Server. " \
268
- f"Service name will be 'None'."
269
+ print_api("SNI Passed: False", color="yellow", **(print_kwargs or {}))
270
+
271
+ message = (
272
+ f"SNI Handler: No SNI was passed, No domain passed from DNS Server. Service name will be 'None'.")
269
273
  print_api(message, color="yellow", **(print_kwargs or {}))
270
274
 
271
275
  # Setting "server_hostname" as a domain.
272
276
  self.sni_received_parameters.ssl_socket.server_hostname = self.sni_received_parameters.destination_name
273
- message = \
274
- f"SNI Handler: port {self.sni_received_parameters.ssl_socket.getsockname()[1]}: " \
275
- f"Incoming connection for [{self.sni_received_parameters.ssl_socket.server_hostname}]"
277
+ print_api("SNI Passed: True", color="yellow", **(print_kwargs or {}))
278
+ message = (
279
+ f"SNI Handler: port {self.sni_received_parameters.ssl_socket.getsockname()[1]}: "
280
+ f"Incoming connection for [{self.sni_received_parameters.ssl_socket.server_hostname}]")
276
281
  print_api(message, **(print_kwargs or {}))
277
282
  except Exception as exception_object:
278
283
  message = f"SNI Handler: Undocumented exception general settings section: {exception_object}"
@@ -1,9 +1,11 @@
1
1
  import multiprocessing
2
2
  import threading
3
3
  import select
4
- from typing import Literal, Union
4
+ from typing import Literal, Union, Callable, Any
5
5
  from pathlib import Path
6
6
  import socket
7
+ import shutil
8
+ import os
7
9
 
8
10
  from ...mitm import initialize_engines
9
11
  from ..psutilw import psutil_networks
@@ -47,7 +49,7 @@ class SocketWrapper:
47
49
  default_server_certificate_name: str = None,
48
50
  default_certificate_domain_list: list = None,
49
51
  default_server_certificate_directory: str = None,
50
- sni_custom_callback_function: callable = None,
52
+ sni_custom_callback_function: Callable[..., Any] = None,
51
53
  sni_use_default_callback_function: bool = False,
52
54
  sni_use_default_callback_function_extended: bool = False,
53
55
  sni_add_new_domains_to_default_server_certificate: bool = False,
@@ -76,9 +78,7 @@ class SocketWrapper:
76
78
  statistics_logger_queue: multiprocessing.Queue = None,
77
79
  exceptions_logger_name: str = 'SocketWrapperExceptions',
78
80
  exceptions_logger_queue: multiprocessing.Queue = None,
79
- no_engine_usage_enable: bool = False,
80
- no_engines_listening_address_list: list[str] = None,
81
- engines_list: list[initialize_engines.ModuleCategory] = None
81
+ print_kwargs: dict = None,
82
82
  ):
83
83
  """
84
84
  Socket Wrapper class that will be used to create sockets, listen on them, accept connections and send them to
@@ -171,22 +171,7 @@ class SocketWrapper:
171
171
  :param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
172
172
  :param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
173
173
  multiprocessing. You need to start the logger listener in the main process to handle the queue.
174
- :param no_engine_usage_enable: boolean, if True, 'engines_list' will be used to listen on the addresses,
175
- but the "no_engines_listening_address_list" parameter will be used instead.
176
- :param no_engines_listening_address_list: list, of ips+ports that will be listened on.
177
- Example: ['0.0.0.0:443', '0.0.0.0:80']
178
- :param engines_list: list, of engines that will be used to process the requests. Structure of engine_config.toml:
179
- [engine]
180
- "domains" = ["example.com"]
181
-
182
- [mtls]
183
- # "subdomain.domain.com" = "file_name_in_current_dir.pem"
184
-
185
- [no_sni]
186
- #get_from_dns = 1 # Blocking, the accept function will wait until the domain is received from DNS.
187
- #get_from_engine = 0
188
- #try_to_get_from_dns_on_empty_get_from_engine = 0 # Non-blocking, on empty DNS server queue, accept() will connect to the domain from below.
189
- #"domain" = "example.com"
174
+ :param print_kwargs: dict, additional arguments to pass to the print function.
190
175
  """
191
176
 
192
177
  self.ip_address: str = ip_address
@@ -202,7 +187,7 @@ class SocketWrapper:
202
187
  self.default_server_certificate_name: str = default_server_certificate_name
203
188
  self.default_certificate_domain_list: list = default_certificate_domain_list
204
189
  self.default_server_certificate_directory: str = default_server_certificate_directory
205
- self.sni_custom_callback_function: callable = sni_custom_callback_function
190
+ self.sni_custom_callback_function: Callable[..., Any] = sni_custom_callback_function
206
191
  self.sni_use_default_callback_function: bool = sni_use_default_callback_function
207
192
  self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
208
193
  self.sni_add_new_domains_to_default_server_certificate: bool = sni_add_new_domains_to_default_server_certificate
@@ -221,9 +206,7 @@ class SocketWrapper:
221
206
  self.ssh_script_to_execute = ssh_script_to_execute
222
207
  self.forwarding_dns_service_ipv4_list___only_for_localhost = (
223
208
  forwarding_dns_service_ipv4_list___only_for_localhost)
224
- # self.no_engine_usage_enable: bool = no_engine_usage_enable
225
- # self.no_engines_listening_address_list: list[str] = no_engines_listening_address_list
226
- # self.engines_list: list[initialize_engines.ModuleCategory] = engines_list
209
+ self.print_kwargs: dict = print_kwargs
227
210
 
228
211
  self.socket_object = None
229
212
 
@@ -313,13 +296,6 @@ class SocketWrapper:
313
296
  "You can't set both [sni_use_default_callback_function = True] and [sni_custom_callback_function]."
314
297
  raise SocketWrapperConfigurationValuesError(message)
315
298
 
316
- # if self.no_engine_usage_enable and not self.no_engines_listening_address_list:
317
- # message = "You set [no_engine_usage_enable = True], but you didn't set [no_engines_listening_address_list]."
318
- # raise SocketWrapperConfigurationValuesError(message)
319
- # elif not self.no_engine_usage_enable and not self.engines_list:
320
- # message = "You set [no_engine_usage_enable = False], but you didn't set [engines_list]."
321
- # raise SocketWrapperConfigurationValuesError(message)
322
-
323
299
  try:
324
300
  booleans.is_only_1_true_in_list(
325
301
  booleans_list_of_tuples=[
@@ -392,6 +368,13 @@ class SocketWrapper:
392
368
  pem_file_path=self.ca_certificate_filepath,
393
369
  crt_file_path=self.ca_certificate_crt_filepath)
394
370
 
371
+ # If someone removed the CA certificate file manually, and now it was created, we also need to
372
+ # clear the cached certificates.
373
+ shutil.rmtree(self.sni_server_certificates_cache_directory)
374
+ os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
375
+ print_api("Removed cached server certificates.", logger=self.logger)
376
+
377
+
395
378
  if self.install_ca_certificate_to_root_store:
396
379
  if not self.ca_certificate_filepath:
397
380
  message = "You set [install_ca_certificate_to_root_store = True],\n" \
@@ -411,7 +394,8 @@ class SocketWrapper:
411
394
  # If there is only one certificate with the same name, check if it is the same certificate.
412
395
  elif is_installed_by_name and len(certificate_list_by_name) == 1:
413
396
  is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
414
- certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True)
397
+ certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
398
+ print_kwargs=self.print_kwargs)
415
399
  # If the certificate is not the same, delete it.
416
400
  if not is_installed_by_file:
417
401
  if not permissions.is_admin():
@@ -427,7 +411,9 @@ class SocketWrapper:
427
411
  if self.install_ca_certificate_to_root_store:
428
412
  # Install CA certificate to the root store if it is not installed.
429
413
  is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
430
- certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True)
414
+ certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
415
+ print_kwargs=self.print_kwargs
416
+ )
431
417
  if not is_installed_by_file:
432
418
  if not permissions.is_admin():
433
419
  raise SocketWrapperConfigurationValuesError(
@@ -448,7 +434,7 @@ class SocketWrapper:
448
434
 
449
435
  def start_listening_socket(
450
436
  self,
451
- callable_function: callable,
437
+ callable_function: Callable[..., Any],
452
438
  callable_args: tuple = ()
453
439
  ):
454
440
  """
@@ -478,7 +464,7 @@ class SocketWrapper:
478
464
  def listening_socket_loop(
479
465
  self,
480
466
  listening_socket_object: socket.socket,
481
- callable_function: callable,
467
+ callable_function: Callable[..., Any],
482
468
  callable_args=()
483
469
  ):
484
470
  """
@@ -688,7 +674,7 @@ class SocketWrapper:
688
674
 
689
675
 
690
676
  def before_socket_thread_worker(
691
- callable_function: callable,
677
+ callable_function: Callable[..., Any],
692
678
  callable_args: tuple,
693
679
  exceptions_logger: loggingw.ExceptionCsvLogger = None
694
680
  ):
@@ -1,32 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: atomicshop
3
- Version: 3.3.1
3
+ Version: 3.3.3
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
- License: MIT License
7
-
8
- Copyright (c) 2023 Bugsec, Denis Kras
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
6
+ License-Expression: MIT
27
7
  Project-URL: Homepage, https://github.com/BugSec-Official/atomicshop
28
8
  Classifier: Intended Audience :: Developers
29
- Classifier: License :: OSI Approved :: MIT License
30
9
  Classifier: Programming Language :: Python :: 3
31
10
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
11
  Requires-Python: >=3.10
@@ -38,7 +17,7 @@ Requires-Dist: cryptography
38
17
  Requires-Dist: dnslib
39
18
  Requires-Dist: dnspython
40
19
  Requires-Dist: docker
41
- Requires-Dist: flask-socketio
20
+ Requires-Dist: flask_socketio
42
21
  Requires-Dist: google-api-python-client
43
22
  Requires-Dist: google-generativeai
44
23
  Requires-Dist: icmplib
@@ -49,16 +28,16 @@ Requires-Dist: pandas
49
28
  Requires-Dist: paramiko
50
29
  Requires-Dist: pefile
51
30
  Requires-Dist: playwright
52
- Requires-Dist: playwright-stealth ==1.0.6
53
31
  Requires-Dist: protobuf
54
32
  Requires-Dist: psutil
55
- Requires-Dist: py7zr ==0.22.0
33
+ Requires-Dist: py7zr==0.22.0
56
34
  Requires-Dist: pyautogui
57
35
  Requires-Dist: pymongo
58
36
  Requires-Dist: pyopenssl
59
37
  Requires-Dist: python-bidi
60
38
  Requires-Dist: python-docx
61
39
  Requires-Dist: python-magic
40
+ Requires-Dist: pywin32; platform_system == "Windows"
62
41
  Requires-Dist: reportlab
63
42
  Requires-Dist: setuptools
64
43
  Requires-Dist: SoundCard
@@ -66,7 +45,7 @@ Requires-Dist: soundfile
66
45
  Requires-Dist: SpeechRecognition
67
46
  Requires-Dist: tldextract
68
47
  Requires-Dist: websockets
69
- Requires-Dist: pywin32 ; platform_system == "Windows"
48
+ Dynamic: license-file
70
49
 
71
50
  <h1 align="center">Atomic Workshop</h1>
72
51
 
@@ -1,9 +1,9 @@
1
- atomicshop/__init__.py,sha256=RhY_EDRbhKW9IqS5dFeb0i0NFF3rHaUDEuvTN0g-Eh4,122
1
+ atomicshop/__init__.py,sha256=A47BWVHvVET6LMVlCSW87U79q8X9M4kwmxOtynSkYqA,122
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
5
5
  atomicshop/appointment_management.py,sha256=BsYH_PClTGLVazcuNjt30--hpXKYjSmHp1R1iQbM4Hc,7330
6
- atomicshop/certificates.py,sha256=MEGj7t3Nt2CHE7yzXrvFTLCOKZG9tJ6Ok5JC2BsFRis,7603
6
+ atomicshop/certificates.py,sha256=c9Z3VQvf7T5OD4sPHRzzfZO1M2gRzyRwpMLiMR06B0w,7942
7
7
  atomicshop/command_line_processing.py,sha256=u5yT9Ger_cu7ni5ID0VFlRbVD46ARHeNC9tRM-_YXrQ,1038
8
8
  atomicshop/config_init.py,sha256=G6_f25zPxyPlht5jJC4ZDrrQiuJfjHiuPd22VBjd9Cg,2683
9
9
  atomicshop/console_output.py,sha256=AOSJjrRryE97PAGtgDL03IBtWSi02aNol8noDnW3k6M,4667
@@ -14,7 +14,7 @@ atomicshop/dns.py,sha256=XB0tijVi1bxWLWKV9hPzpt75jK4SrGbZCV5VJbiiQ74,7185
14
14
  atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
15
15
  atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
16
16
  atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
17
- atomicshop/filesystem.py,sha256=owVG49AtC0-6KCYd3bORwTS44xj4NlfktD70QpIQMrM,68030
17
+ atomicshop/filesystem.py,sha256=tCPRA6SQiJJkFL3BnPCoVhPCkvrkI2C4RyuSKzuxo1w,68060
18
18
  atomicshop/functions.py,sha256=pK8hoCE9z61PtWCxQJsda7YAphrLH1wxU5x-1QJP-sY,499
19
19
  atomicshop/get_process_list.py,sha256=8cxb7gKe9sl4R6H2yMi8J6oe-RkonTvCdKjRFqi-Fs4,6075
20
20
  atomicshop/get_process_name_cmd_dll.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
@@ -23,7 +23,7 @@ atomicshop/http_parse.py,sha256=1Tna9YbOM0rE3t6i_M-klBlwd1KNSA9skA_BqKGXDFc,1186
23
23
  atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
24
24
  atomicshop/ip_addresses.py,sha256=penRFeJ1-LDVTko4Q0EwK4JiN5cU-KzCBR2VXg9qbUY,1238
25
25
  atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
26
- atomicshop/networks.py,sha256=xOU_lDf6Rct178W7EB80-AFMgu9Nnh6l7GgjA9z9Jtg,19010
26
+ atomicshop/networks.py,sha256=dTh6T9vAnjYOEPdfpL6W6wN091HwRbVLeNscXWZX_DI,22151
27
27
  atomicshop/on_exit.py,sha256=9XlOnzoAG8zlI8wBF4AB8hyrC6Q1b84gkhqpAhhdN9g,6977
28
28
  atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
29
29
  atomicshop/print_api.py,sha256=SJNQIMqSLlYaPtjHnALySAI-jQYuYHOCGgfP7oe96fU,10957
@@ -91,7 +91,7 @@ atomicshop/archiver/shutils.py,sha256=BomnK7zT-nQXA1z0i2R2aTv8eu88wPx7tf2HtOdbmE
91
91
  atomicshop/archiver/zips.py,sha256=0Z_1MWs7YRiCBVpyaG8llnzRguHSO4R51KDMN3FJZt8,16984
92
92
  atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  atomicshop/basics/ansi_escape_codes.py,sha256=uGVRW01v2O052701sOfAdCaM_GLF_cu_jrssuYiPbzA,3216
94
- atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
94
+ atomicshop/basics/argparse_template.py,sha256=NRz63v2jr4tHcAGvqW1okYsGRt6n0u8FXuMQQT3dHPw,6071
95
95
  atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
96
96
  atomicshop/basics/bytes_arrays.py,sha256=xfFW9CBQyzf0iXNWpx-EfrxtN3-tiKzuczPzOb6cOdU,7190
97
97
  atomicshop/basics/classes.py,sha256=dBSQktjc1GmphpD_qOGYNDl-mQcmwg8OkhSqtXPfHEU,15301
@@ -137,10 +137,10 @@ atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
137
  atomicshop/mitm/config_static.py,sha256=yNlELenRvLvW37dl2zXstts5zLklay9xF7sVu_AytKM,8839
138
138
  atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
139
139
  atomicshop/mitm/connection_thread_worker.py,sha256=50RP7De2t0WlUk4Ywmv6B63GwvtvoJ9mULo9Q3b-zcY,32992
140
- atomicshop/mitm/import_config.py,sha256=gnm_eVkBg3pmLf_iZL_JiMWute2WVLH_AbAb-TCZiHY,18159
141
- atomicshop/mitm/initialize_engines.py,sha256=dO8u2pffixLDN_evZy0yJpnjmTx_J23cLnoHVLj5qbY,10800
140
+ atomicshop/mitm/import_config.py,sha256=J3ZLF28AsKu1h76iRV_sCM52g0oh4dwDGddZl_XcJsU,18351
141
+ atomicshop/mitm/initialize_engines.py,sha256=qzz5jzh_lKC03bI1w5ebngVXo1K-RV3poAyW-nObyqo,11042
142
142
  atomicshop/mitm/message.py,sha256=CDhhm4BTuZE7oNZCjvIZ4BuPOW4MuIzQLOg91hJaxDI,3065
143
- atomicshop/mitm/mitm_main.py,sha256=8xoyKn-Us1fmddDwt1_8_fkvaSwsd_mmM1Y-hT_KvWA,37244
143
+ atomicshop/mitm/mitm_main.py,sha256=ucmdCr2p1F4c18DE8qJG3uG3FkkVLWdMEIWevN5BNKE,38967
144
144
  atomicshop/mitm/recs_files.py,sha256=tv8XFhYZMkBv4DauvpiAdPgvSo0Bcm1CghnmwO7dx8M,5018
145
145
  atomicshop/mitm/shared_functions.py,sha256=0lzeyINd44sVEfFbahJxQmz6KAMWbYrW5ou3UYfItvw,1777
146
146
  atomicshop/mitm/statistic_analyzer.py,sha256=5_sAYGX2Xunzo_pS2W5WijNCwr_BlGJbbOO462y_wN4,27533
@@ -195,7 +195,7 @@ atomicshop/wrappers/_process_wrapper_curl.py,sha256=XkZZXYl7D0Q6UfdWqy-18AvpU0yV
195
195
  atomicshop/wrappers/_process_wrapper_tar.py,sha256=WUMZFKNrlG4nJP9tWZ51W7BR1j_pIjsjgyAStmWjRGs,655
196
196
  atomicshop/wrappers/astw.py,sha256=VkYfkfyc_PJLIOxByT6L7B8uUmKY6-I8XGZl4t_z828,4239
197
197
  atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK_tJuSgU,22800
198
- atomicshop/wrappers/cryptographyw.py,sha256=LfzTnwvJE03G6WZryOOf43VKhhnyMakzHpn8DPPCoy4,13252
198
+ atomicshop/wrappers/cryptographyw.py,sha256=QEUpDn8vUvMg3ADz6-4oC2kbDNC_woDlw7C0zU7qFVM,14233
199
199
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
200
200
  atomicshop/wrappers/githubw.py,sha256=DrFF_oN-rulPQV1iKgVzZadCjuYuCC5eKAjZp_3YD0g,23476
201
201
  atomicshop/wrappers/msiw.py,sha256=GQLqud72nfex3kvO1bJSruNriCYTYX1_G1gSf1MPkIA,6118
@@ -204,11 +204,11 @@ atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc
204
204
  atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
205
205
  atomicshop/wrappers/pipw.py,sha256=mu4jnHkSaYNfpBiLZKMZxEX_E2LqW5BVthMZkblPB_c,1317
206
206
  atomicshop/wrappers/process_wrapper_pbtk.py,sha256=ycPmBRnv627RWks6N8OhxJQe8Gu3h3Vwj-4HswPOw0k,599
207
- atomicshop/wrappers/pyopensslw.py,sha256=OBWxA6EJ2vU_Qlf4M8m6ilcG3hyYB4yB0EsXUf7NhEU,6804
207
+ atomicshop/wrappers/pyopensslw.py,sha256=pklvXEbi0fHu5n1eRKKHEDLN_nsIqCTXv5Lv0bzReTo,7071
208
208
  atomicshop/wrappers/sysmonw.py,sha256=CdawuWuy_uUi3ALCm6lKP7pSyKeTk1MXyzOaTMbBSO8,5346
209
209
  atomicshop/wrappers/ubuntu_terminal.py,sha256=vGEBznz_EKzjhsFAXzefep3SwdP2toYPxWkagOjME3I,12328
210
210
  atomicshop/wrappers/wslw.py,sha256=2Z7X0j5M2hoRZjbHfm_vqwNXZeptsdkNCdhdcM_S9vo,6998
211
- atomicshop/wrappers/certauthw/certauth.py,sha256=hKedW0DOWlEigSNm8wu4SqHkCQsGJ1tJfH7s4nr3Bk0,12223
211
+ atomicshop/wrappers/certauthw/certauth.py,sha256=qLo593_MLU8VfbhYoNQ3J5BvtZuE71aFQROysEt6_dk,12225
212
212
  atomicshop/wrappers/certauthw/certauthw.py,sha256=4WvhjANI7Kzqrr_nKmtA8Kf7B6rute_5wfP65gwQrjw,8082
213
213
  atomicshop/wrappers/ctyping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
214
214
  atomicshop/wrappers/ctyping/file_details_winapi.py,sha256=dmdnCMwx4GgVEctiUIyniVShAoXmv_bZu_o81C450PY,2810
@@ -225,7 +225,7 @@ atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py,sha256=AEk
225
225
  atomicshop/wrappers/ctyping/msi_windows_installer/tables.py,sha256=tHsu0YfBgzuIk9L-PyqLgU_IzyVbCfy8L1EqelNnvWk,17674
226
226
  atomicshop/wrappers/dockerw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
227
227
  atomicshop/wrappers/dockerw/dockerw.py,sha256=GgPSvXxJj15kZ-LPiaHLl8aekof53sSP_U-vUMUe7_8,10639
228
- atomicshop/wrappers/dockerw/install_docker.py,sha256=CeDlxWuOn_bRUhHEnvpgVGGYZgP7B-Q9qNMkDfFiV2E,10073
228
+ atomicshop/wrappers/dockerw/install_docker.py,sha256=9fjbx3GtpnNA4d4YU2ziPynqANXxo-x-Sq90SUSQEPg,18448
229
229
  atomicshop/wrappers/elasticsearchw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
230
  atomicshop/wrappers/elasticsearchw/config_basic.py,sha256=fDujtrjEjbWiYh_WQ3OcYp_8mXhXPYeKLy4wSPL5qM0,1177
231
231
  atomicshop/wrappers/elasticsearchw/elastic_infra.py,sha256=at0sD-SFtmEvfGyIU_YBEKoU-MNeVtDQSNscPm0JWLc,10368
@@ -284,7 +284,7 @@ atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
284
284
  atomicshop/wrappers/playwrightw/_tryouts.py,sha256=l1BLkFsiIMNlgv7nfZd1XGEvXQkIQkIcg48__9OaC00,4920
285
285
  atomicshop/wrappers/playwrightw/base.py,sha256=WeRpx8otdXuKSr-BjY-uCJTze21kbPpfitoOjKQz5-g,9818
286
286
  atomicshop/wrappers/playwrightw/combos.py,sha256=215y7wugyyBrFK9_0WutnMXsF1jqJ2PLm9eHEa2PMz0,7145
287
- atomicshop/wrappers/playwrightw/engine.py,sha256=fapOeaqSMNoxJucTvhzp30cKfKNSVe45drhZJcJ-fOk,15191
287
+ atomicshop/wrappers/playwrightw/engine.py,sha256=sw8TfQBNn-v0kMYDtmaWAA0wXndM57-4TMteWMw0jnA,15195
288
288
  atomicshop/wrappers/playwrightw/infra.py,sha256=ncp62l6CgAgLz4lqBtKEWZO_6ES8cqnbzJBov-tlpBo,97
289
289
  atomicshop/wrappers/playwrightw/javascript.py,sha256=_bW7CAtm0Y8IHYrAalg5HpPFnk6juiqicmkvZ9dITaA,1235
290
290
  atomicshop/wrappers/playwrightw/keyboard.py,sha256=zN3YddGO-qUkn6C0CRVFejP4cTuaUwXLDNFhFREjERY,422
@@ -300,7 +300,7 @@ atomicshop/wrappers/psutilw/psutil_networks.py,sha256=79FplDAj45ofBCudlft77O3lfi
300
300
  atomicshop/wrappers/psutilw/psutilw.py,sha256=q3EwgprqyrR4zLCjl4l5DHFOQoukEvQMIPjNB504oQ0,21262
301
301
  atomicshop/wrappers/psycopgw/psycopgw.py,sha256=XJvVf0oAUjCHkrYfKeFuGCpfn0Oxj3u4SbKMKA1508E,7118
302
302
  atomicshop/wrappers/pycharmw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
303
- atomicshop/wrappers/pycharmw/ubuntu.py,sha256=m9MpgqvIYygulhPxo9g2zlGGXrihBpiY3GNLNyT-B7U,1290
303
+ atomicshop/wrappers/pycharmw/ubuntu.py,sha256=vOvGWvTbzvTja9tRrJW2yJb0_r1EV11RrENGHckzvVE,1051
304
304
  atomicshop/wrappers/pycharmw/win.py,sha256=hNP-d95z1zhcCpYqhHE5HZVYxaAlt8JJCNXh65jZsHc,2757
305
305
  atomicshop/wrappers/pywin32w/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
306
306
  atomicshop/wrappers/pywin32w/cert_store.py,sha256=dV1XyoKTFKZ-HCIVqU2Nd6CTZ8HANqjAXv26rsNzO6s,4365
@@ -328,18 +328,18 @@ atomicshop/wrappers/socketw/exception_wrapper.py,sha256=qW_1CKyPgGlsIt7_jusKkMV4
328
328
  atomicshop/wrappers/socketw/get_process.py,sha256=aJC-_qFUv3NgWCSUzDI72E4z8_-VTZE9NVZ0CwUoNlM,5698
329
329
  atomicshop/wrappers/socketw/receiver.py,sha256=9B3MvcDqr4C3x2fsnjG5SQognd1wRqsBgikxZa0wXG8,8243
330
330
  atomicshop/wrappers/socketw/sender.py,sha256=aX_K8l_rHjd5AWb8bi5mt8-YTkMYVRDB6DnPqK_XDUE,4754
331
- atomicshop/wrappers/socketw/sni.py,sha256=q-F-R1KtE94g8WGrR3QHgi-otXZJUPBprEwQqnY80_A,17639
331
+ atomicshop/wrappers/socketw/sni.py,sha256=GaYnn-CFFmA1WcyFXqICnSLfkM2CNAeS8fTZsruLoaI,17927
332
332
  atomicshop/wrappers/socketw/socket_client.py,sha256=McBd3DeCy787oDGCEMUEP2awWy3vdkPqr9w-aFh2fBM,22502
333
333
  atomicshop/wrappers/socketw/socket_server_tester.py,sha256=Qobmh4XV8ZxLUaw-eW4ESKAbeSLecCKn2OWFzMhadk0,6420
334
- atomicshop/wrappers/socketw/socket_wrapper.py,sha256=NRr64RHrKPG049MplW7_eTtpZzJxBc2Nx_XNfuwP_Hk,42197
334
+ atomicshop/wrappers/socketw/socket_wrapper.py,sha256=u_v0pjMMrgsdtI0iPPddiLe2wXnBoqTgNM9Y3zjGD4U,41013
335
335
  atomicshop/wrappers/socketw/ssl_base.py,sha256=62-hPm7zla1rh3m_WvDnXqKH-sDUTdiRptD8STCkgdk,2313
336
336
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=_gA8bMX6Sw_UCXKi2y9wNAwlqifgExgDGfQIa9pFxQA,5543
337
337
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
338
338
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
339
339
  atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
340
- atomicshop-3.3.1.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
- atomicshop-3.3.1.dist-info/METADATA,sha256=RW937Udw4KjGiOk4f_Am7NJ_NHvaEWfu9rYXjjKoinc,10602
342
- atomicshop-3.3.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
343
- atomicshop-3.3.1.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
344
- atomicshop-3.3.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
345
- atomicshop-3.3.1.dist-info/RECORD,,
340
+ atomicshop-3.3.3.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
+ atomicshop-3.3.3.dist-info/METADATA,sha256=_zTFWmzEYji52hVTJBoGFrdrjSxlo6wS1AhAhq0qHqQ,9288
342
+ atomicshop-3.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
343
+ atomicshop-3.3.3.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
344
+ atomicshop-3.3.3.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
345
+ atomicshop-3.3.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5