atomicshop 3.3.28__py3-none-any.whl → 3.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (99) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  3. atomicshop/a_mains/install_ca_certificate.py +172 -0
  4. atomicshop/a_mains/process_from_port.py +119 -0
  5. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  6. atomicshop/basics/strings.py +1 -1
  7. atomicshop/certificates.py +2 -2
  8. atomicshop/dns.py +26 -28
  9. atomicshop/etws/traces/trace_tcp.py +1 -2
  10. atomicshop/mitm/centered_settings.py +133 -0
  11. atomicshop/mitm/config_static.py +18 -43
  12. atomicshop/mitm/connection_thread_worker.py +376 -162
  13. atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
  14. atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
  15. atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
  16. atomicshop/mitm/engines/create_module_template.py +1 -2
  17. atomicshop/mitm/import_config.py +79 -88
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +222 -121
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/networks.py +108 -93
  24. atomicshop/package_mains_processor.py +84 -0
  25. atomicshop/permissions/ubuntu_permissions.py +47 -0
  26. atomicshop/print_api.py +3 -5
  27. atomicshop/python_functions.py +23 -108
  28. atomicshop/speech_recognize.py +8 -0
  29. atomicshop/ssh_remote.py +115 -51
  30. atomicshop/web.py +20 -7
  31. atomicshop/web_apis/google_llm.py +22 -14
  32. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  33. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  34. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  35. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  36. atomicshop/wrappers/githubw.py +175 -63
  37. atomicshop/wrappers/loggingw/handlers.py +1 -1
  38. atomicshop/wrappers/loggingw/loggingw.py +17 -1
  39. atomicshop/wrappers/netshw.py +124 -3
  40. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  41. atomicshop/wrappers/powershell_networking.py +80 -0
  42. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  43. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  44. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  45. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  46. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  47. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  48. atomicshop/wrappers/socketw/certificator.py +19 -9
  49. atomicshop/wrappers/socketw/creator.py +30 -7
  50. atomicshop/wrappers/socketw/dns_server.py +6 -6
  51. atomicshop/wrappers/socketw/exception_wrapper.py +3 -3
  52. atomicshop/wrappers/socketw/process_getter.py +86 -0
  53. atomicshop/wrappers/socketw/receiver.py +29 -9
  54. atomicshop/wrappers/socketw/sender.py +10 -9
  55. atomicshop/wrappers/socketw/sni.py +23 -6
  56. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  57. atomicshop/wrappers/socketw/socket_client.py +6 -8
  58. atomicshop/wrappers/socketw/socket_wrapper.py +82 -21
  59. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  60. atomicshop/wrappers/win_auditw.py +189 -0
  61. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  62. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/RECORD +74 -88
  63. atomicshop/_basics_temp.py +0 -101
  64. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  65. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  66. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  67. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  68. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  69. atomicshop/addons/package_setup/Setup.cmd +0 -7
  70. atomicshop/archiver/__init__.py +0 -0
  71. atomicshop/archiver/_search_in_zip.py +0 -189
  72. atomicshop/archiver/search_in_archive.py +0 -284
  73. atomicshop/archiver/sevenz_app_w.py +0 -86
  74. atomicshop/archiver/sevenzs.py +0 -73
  75. atomicshop/archiver/shutils.py +0 -34
  76. atomicshop/archiver/zips.py +0 -353
  77. atomicshop/file_types.py +0 -24
  78. atomicshop/pbtkmultifile_argparse.py +0 -88
  79. atomicshop/script_as_string_processor.py +0 -42
  80. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  81. atomicshop/ssh_scripts/process_from_port.py +0 -27
  82. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  83. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  84. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  85. atomicshop/wrappers/ffmpegw.py +0 -125
  86. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  87. atomicshop/wrappers/socketw/get_process.py +0 -123
  88. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  89. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  90. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  91. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  92. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  93. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  94. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  95. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  96. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  97. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  98. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  99. {atomicshop-3.3.28.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,82 @@
1
+ import socket
2
+
3
+ import paramiko
4
+
5
+ from .. import package_mains_processor, ssh_remote, config_init
6
+ from ..wrappers.socketw import process_getter
7
+ from ..print_api import print_api
8
+
9
+
10
+ PORT_TO_CMD_FILE: str = 'process_from_port'
11
+ TCP_PORTS_FILE: str = 'get_local_tcp_ports'
12
+
13
+
14
+ def test_ssh_main(config: dict) -> int:
15
+ hosts: list = config['main']['hosts_or_ips']
16
+
17
+ for host in hosts:
18
+ print("-----------------------------------")
19
+ print_api(f"Testing cmd for host: {host}", color='blue')
20
+
21
+ if host in config['main']:
22
+ print("Using host-specific credentials")
23
+ username = config[host]['user']
24
+ password = config[host]['pass']
25
+ else:
26
+ print("Didn't find host-specific credential, using defaults")
27
+ username = config['all_hosts']['user']
28
+ password = config['all_hosts']['pass']
29
+
30
+ ssh_client = ssh_remote.SSHRemote(ip_address=host, username=username, password=password)
31
+
32
+ try:
33
+ ssh_client.connect()
34
+ except socket.gaierror as e:
35
+ if e.errno == 11001:
36
+ print_api(f"Couldn't resolve IP to {host}: {str(e)}\n"
37
+ f"Try providing IP address instead of hostname", color='red')
38
+ continue
39
+ else:
40
+ raise e
41
+ except paramiko.ssh_exception.NoValidConnectionsError as e:
42
+ print_api(f"Couldn't connect to {host}: {str(e)}", color='red')
43
+ continue
44
+
45
+ # Read the TCP ports file to string.
46
+ tcp_ports_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
47
+ script_file_stem=TCP_PORTS_FILE)
48
+ tcp_ports_script_string: str = tcp_ports_package_processor.read_script_file_to_string()
49
+
50
+ # Execute the TCP ports script remotely via SSH to get the list of open TCP ports.
51
+ tcp_ports_output, tcp_ports_error = ssh_client.remote_execution_python(script_string=tcp_ports_script_string)
52
+ if tcp_ports_error:
53
+ print_api(f"Error getting TCP ports from host {host}: {tcp_ports_error}", color='red')
54
+ continue
55
+
56
+ tcp_ports_list: list = tcp_ports_output.strip().splitlines()
57
+ if not tcp_ports_list:
58
+ print_api(f"No TCP ports found on host {host}", color='red')
59
+ continue
60
+
61
+ last_port: int = int(tcp_ports_list[-1])
62
+
63
+ port_to_cmd_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
64
+ script_file_stem=PORT_TO_CMD_FILE)
65
+ get_command_instance = process_getter.GetCommandLine(
66
+ client_ip=host,
67
+ client_port=last_port,
68
+ package_processor=port_to_cmd_package_processor,
69
+ ssh_client=ssh_client)
70
+ process_name = get_command_instance.get_process_name()
71
+ print(f"Process for port {last_port} on host {host}: {process_name}")
72
+
73
+ print("Closing SSH connection")
74
+ ssh_client.close()
75
+
76
+ if not process_name:
77
+ print_api(f"Failed to get process name for port {last_port} on host {host}", color='red')
78
+ continue
79
+
80
+ print_api(f"SSH test success!", color='green')
81
+
82
+ return 0
atomicshop/networks.py CHANGED
@@ -1,18 +1,23 @@
1
1
  import socket
2
2
  import time
3
- from typing import Union
3
+ from typing import Union, Literal
4
4
  import os
5
5
  import psutil
6
6
  import ctypes
7
+ from logging import Logger
8
+ import subprocess
7
9
 
8
10
  from icmplib import ping
9
11
  from icmplib.models import Host
10
12
  from win32com.client import CDispatch
11
13
 
12
- from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers, msft_netipaddress
14
+ from .print_api import print_api
15
+ from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers
13
16
  from .wrappers.ctyping import setup_device
14
17
  from .wrappers.winregw import winreg_network
15
18
  from .wrappers.psutilw import psutil_networks
19
+ from .wrappers import powershell_networking, netshw
20
+ from .wrappers.socketw import socket_base
16
21
 
17
22
 
18
23
  MICROSOFT_LOOPBACK_DEVICE_NAME: str = 'Microsoft KM-TEST Loopback Adapter'
@@ -61,6 +66,7 @@ def is_ip_in_use_arp(
61
66
  def _send_arp(ip: str) -> str | None:
62
67
  """Return MAC string like 'aa:bb:cc:dd:ee:ff' if IP is claimed on the LAN, else None."""
63
68
  # inet_addr returns DWORD in network byte order
69
+ # noinspection PyUnresolvedReferences
64
70
  dest_ip = ws2_32.inet_addr(ip.encode('ascii'))
65
71
  if dest_ip == 0xFFFFFFFF: # INVALID
66
72
  raise ValueError(f"Bad IPv4 address: {ip}")
@@ -68,6 +74,7 @@ def is_ip_in_use_arp(
68
74
  mac_buf = ctypes.c_uint64(0) # storage for up to 8 bytes
69
75
  mac_len = ctypes.c_ulong(ctypes.sizeof(mac_buf)) # in/out len
70
76
  # SrcIP=0 lets Windows pick the right interface
77
+ # noinspection PyUnresolvedReferences
71
78
  rc = iphlpapi.SendARP(dest_ip, 0, ctypes.byref(mac_buf), ctypes.byref(mac_len))
72
79
  if rc != 0: # Non-zero means no ARP reply / not on-link / other error
73
80
  return None
@@ -105,32 +112,34 @@ def get_default_internet_ipv4(target: str = "8.8.8.8") -> str:
105
112
  return s.getsockname()[0] # local address of that route
106
113
 
107
114
 
108
- def get_hostname() -> str:
115
+ def get_default_interface_name() -> str:
116
+ default_connection_name_dict: dict = psutil_networks.get_default_connection_name()
117
+ if not default_connection_name_dict:
118
+ return ""
119
+ # Get the first key from the dictionary.
120
+ connection_name: str = list(default_connection_name_dict.keys())[0]
121
+ return connection_name
122
+
123
+
124
+ def list_network_interfaces() -> list[str]:
109
125
  """
110
- Get the default network interface name that is being used for internet.
111
- :return: string, default network interface name.
126
+ List all network interfaces on the system.
127
+ :return: list of strings, network interface names.
112
128
  """
113
129
 
114
- return socket.gethostname()
130
+ return psutil_networks.list_network_interfaces()
115
131
 
116
132
 
117
- def get_default_internet_interface_name() -> str | None:
133
+ def get_hostname() -> str:
118
134
  """
119
135
  Get the default network interface name that is being used for internet.
120
136
  :return: string, default network interface name.
121
137
  """
122
138
 
123
- interface_dict: dict = psutil_networks.get_default_connection_name()
124
- if not interface_dict:
125
- result = None
126
- else:
127
- # Get the first interface name from the dictionary.
128
- result = next(iter(interface_dict.keys()), None)
129
-
130
- return result
139
+ return socket.gethostname()
131
140
 
132
141
 
133
- def get_interface_ips(
142
+ def get_interface_ips_psutil(
134
143
  interface_name: str = None,
135
144
  ipv4: bool = True,
136
145
  ipv6: bool = True,
@@ -144,7 +153,7 @@ def get_interface_ips(
144
153
 
145
154
  if default_interface:
146
155
  # Get the default interface name.
147
- interface_name = get_default_internet_interface_name()
156
+ interface_name = get_default_interface_name()
148
157
 
149
158
  physical_ip_types: list[str] = []
150
159
  if ipv4:
@@ -169,7 +178,7 @@ def get_interface_ips(
169
178
  return ips
170
179
 
171
180
 
172
- def get_host_ips(
181
+ def get_host_ips_psutil(
173
182
  localhost: bool = True,
174
183
  ipv4: bool = True,
175
184
  ipv6: bool = True
@@ -222,6 +231,22 @@ def get_host_ips(
222
231
  return ip_list
223
232
 
224
233
 
234
+ def get_interface_ips_powershell(
235
+ interface_name: str = None,
236
+ ip_type: Literal["virtual", "dynamic", "all"] = "virtual"
237
+ ) -> list[str]:
238
+ """
239
+ Get the IP addresses of a network interface using PowerShell.
240
+
241
+ :param interface_name: string, name of the network interface.
242
+ If None, all interfaces will be queried.
243
+ :param ip_type: string, type of IP addresses to retrieve.
244
+ :return: list of strings, IP addresses of the network interface.
245
+ """
246
+
247
+ return powershell_networking.get_interface_ips(interface_name=interface_name, ip_type=ip_type)
248
+
249
+
225
250
  def get_microsoft_loopback_device_network_configuration(
226
251
  wmi_instance: CDispatch = None,
227
252
  timeout: int = 1,
@@ -356,17 +381,14 @@ def change_interface_metric_restart_device(
356
381
 
357
382
 
358
383
  def get_wmi_network_adapter_configuration(
359
- use_default_interface: bool = False,
360
- connection_name: str = None,
384
+ interface_name: str = None,
361
385
  mac_address: str = None,
362
386
  wmi_instance: CDispatch = None,
363
387
  get_info_from_network_config: bool = True
364
388
  ) -> tuple:
365
389
  """
366
390
  Get the WMI network configuration for a network adapter.
367
- :param use_default_interface: bool, if True, the default network interface will be used.
368
- This is the adapter that your internet is being used from.
369
- :param connection_name: string, adapter name as shown in the network settings.
391
+ :param interface_name: string, adapter name as shown in the network settings.
370
392
  :param mac_address: string, MAC address of the adapter. Format: '00:00:00:00:00:00'.
371
393
  :param wmi_instance: WMI instance. You can get it from:
372
394
  wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
@@ -377,14 +399,14 @@ def get_wmi_network_adapter_configuration(
377
399
  """
378
400
 
379
401
  wmi_network_config, wmi_network_adapter = win32_networkadapterconfiguration.get_adapter_network_configuration(
380
- use_default_interface=use_default_interface,
381
- connection_name=connection_name,
402
+ interface_name=interface_name,
382
403
  mac_address=mac_address,
383
404
  wmi_instance=wmi_instance
384
405
  )
385
406
 
386
407
  if get_info_from_network_config:
387
408
  adapter_info: dict = win32_networkadapterconfiguration.get_info_from_network_config(wmi_network_config)
409
+ adapter_info['name'] = wmi_network_adapter.NetConnectionID
388
410
  else:
389
411
  adapter_info: dict = {}
390
412
 
@@ -468,7 +490,7 @@ def generate_unused_ipv4_addresses_from_ip(
468
490
  return generated_ips, masks_for_ips
469
491
 
470
492
 
471
- def set_dynamic_ip_for_adapter(
493
+ def set_dynamic_ip_for_adapter_wmi(
472
494
  network_config: CDispatch,
473
495
  reset_dns: bool = True,
474
496
  reset_wins: bool = True
@@ -484,7 +506,7 @@ def set_dynamic_ip_for_adapter(
484
506
  nic_cfg=network_config, reset_dns=reset_dns, reset_wins=reset_wins)
485
507
 
486
508
 
487
- def set_static_ip_for_adapter(
509
+ def set_static_ip_for_adapter_wmi(
488
510
  network_config: CDispatch,
489
511
  ips: list[str],
490
512
  masks: list[str],
@@ -512,19 +534,19 @@ def set_static_ip_for_adapter(
512
534
  )
513
535
 
514
536
 
515
- def add_virtual_ips_to_default_adapter_by_current_setting(
537
+ def add_virtual_ips_to_network_interface(
538
+ interface_name: str,
516
539
  number_of_ips: int = 0,
517
540
  virtual_ipv4s_to_add: list[str] = None,
518
541
  virtual_ipv4_masks_to_add: list[str] = None,
519
542
  set_virtual_ips_skip_as_source: bool = True,
520
- gateways: list[str] | None = None,
521
- dns_gateways: list[str] | None = None,
522
- availability_wait_seconds: int = 15,
523
543
  simulate_only: bool = False,
524
544
  locator: CDispatch = None,
525
545
  wait_until_applied: bool = True,
526
- wait_until_applied_seconds: int = 15
527
- ) -> tuple[list[str], list[str], list[str], list[str]]:
546
+ wait_until_applied_seconds: int = 15,
547
+ verbose: bool = False,
548
+ logger: Logger = None,
549
+ ) -> tuple[list[str], list[str]] | None:
528
550
  """
529
551
  Add virtual IP addresses to the default network adapter.
530
552
  The adapter will set to static IP and DNS gateway, instead of dynamic DHCP.
@@ -535,6 +557,8 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
535
557
  While generating the IPs, the function will skip the already existing IPs in the adapter, like default gateway
536
558
  and DNS servers.
537
559
 
560
+ :param interface_name: string, adapter name as shown in the network settings.
561
+
538
562
  :param number_of_ips: int, number of IPs to generate in addition to the IPv4s that already exist in the adapter.
539
563
  Or you add the IPs and masks to the adapter with the parameters virtual_ipv4s_to_add and virtual_ipv4_masks_to_add.
540
564
 
@@ -544,15 +568,11 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
544
568
 
545
569
  :param set_virtual_ips_skip_as_source: bool, if True, the SkipAsSource flag will be set for the virtual IPs.
546
570
  This is needed to avoid the endless accept() loop.
547
- :param gateways: list of strings, default IPv4 gateways to assign.
548
- None: The already existing gateways in the adapter will be used.
549
- []: No gateways will be assigned.
550
- :param dns_gateways: list of strings, IPv4 DNS servers to assign.
551
- None: The already existing DNS servers in the adapter will be used.
552
- []: No DNS servers will be assigned.
553
- :param availability_wait_seconds: int, seconds to wait for the adapter to be available after setting the IP address.
554
- :param simulate_only: bool, if True, the function will only prepare the ip addresses and return them without changing anything.
555
- :param locator: CDispatch, WMI locator object. If not specified, it will be created.
571
+
572
+ :param simulate_only: bool, if True, the function will only simulate the addition of the IP addresses.
573
+ No changes will be made to the system.
574
+ :param locator: CDispatch, WMI locator object. You can get it from:
575
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
556
576
 
557
577
  :param wait_until_applied: bool, if True, the function will wait until the IP addresses are applied.
558
578
  By default, while WMI command is executed, there is no indication if the addresses were finished applying or not.
@@ -562,7 +582,10 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
562
582
  after setting the IP addresses. This is the time to wait for the IP addresses to be
563
583
  applied after setting them. If the IP addresses are not applied in this time, a TimeoutError will be raised.
564
584
 
565
- :return: tuple of lists, (current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign)
585
+ :param verbose: bool, if True, the function will print verbose output.
586
+ :param logger: Logger, if provided, the function will log messages to this logger.
587
+
588
+ :return: tuple of lists, (ips_to_assign, masks_to_assign)
566
589
  """
567
590
 
568
591
  if virtual_ipv4s_to_add and not virtual_ipv4_masks_to_add:
@@ -579,24 +602,19 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
579
602
  # Connect to WMi.
580
603
  wmi_civ2_instance, locator = wmi_helpers.get_wmi_instance(locator=locator)
581
604
 
582
- # initial_default_ipv4: str = socket.gethostbyname(socket.gethostname())
583
-
584
- # Get the default network adapter configuration.
585
- default_network_adapter_config, default_network_adapter, default_adapter_info = get_wmi_network_adapter_configuration(
586
- use_default_interface=True, wmi_instance=wmi_civ2_instance, get_info_from_network_config=True)
605
+ # Get the network adapter configuration.
606
+ network_adapter_config, network_adapter, adapter_info = get_wmi_network_adapter_configuration(
607
+ interface_name=interface_name, wmi_instance=wmi_civ2_instance, get_info_from_network_config=True)
587
608
 
588
- current_ipv4s: list[str] = default_adapter_info['ipv4s']
589
- current_ipv4_masks: list[str] = default_adapter_info['ipv4_subnet_masks']
590
-
591
- # print(f"Current IPs: {current_ipv4s}")
592
- # current_ips_count: int = len(current_ipv4s)
609
+ current_ipv4s: list[str] = adapter_info['ipv4s']
610
+ current_ipv4_masks: list[str] = adapter_info['ipv4_subnet_masks']
593
611
 
594
612
  if number_of_ips > 0:
595
613
  ips_to_assign, masks_to_assign = generate_unused_ipv4_addresses_from_ip(
596
614
  ip_address=current_ipv4s[0],
597
615
  mask=current_ipv4_masks[0],
598
616
  number_of_ips=number_of_ips,
599
- skip_ips=current_ipv4s + default_adapter_info['default_gateways'] + default_adapter_info['dns_gateways']
617
+ skip_ips=current_ipv4s + adapter_info['default_gateways'] + adapter_info['dns_gateways']
600
618
  )
601
619
  elif virtual_ipv4s_to_add and virtual_ipv4_masks_to_add:
602
620
  ips_to_assign = virtual_ipv4s_to_add
@@ -606,51 +624,48 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
606
624
  masks_to_assign = []
607
625
 
608
626
  if not simulate_only:
609
- # Final list of IPs to assign.
610
- ips: list[str] = current_ipv4s + ips_to_assign
611
- masks: list[str] = current_ipv4_masks + masks_to_assign
612
-
613
- # ---------- IP assignment --------------------------------------------
614
- if ips != current_ipv4s:
615
- # print("[+] Setting static IPv4 addresses …")
616
- if gateways is None:
617
- gateways = default_adapter_info['default_gateways']
618
-
619
- if dns_gateways is None:
620
- dns_gateways = default_adapter_info['dns_gateways']
621
-
622
- # We will get the default IP address of the machine.
623
- default_ip_address: str = get_default_internet_ipv4()
624
- # So we can make it the first IP in the list, but first remove it from the list.
625
- _ = ips.pop(ips.index(default_ip_address))
626
- # At this point we will copy the list of IPs that we will set the SkipAsSource flag for.
627
- ips_for_skip_as_source = ips.copy()
628
- # Add it back to the beginning of the list.
629
- ips.insert(0, default_ip_address)
630
-
631
- win32_networkadapterconfiguration.set_static_ips(
632
- default_network_adapter_config, ips=ips, masks=masks,
633
- gateways=gateways, dns_gateways=dns_gateways,
634
- availability_wait_seconds=availability_wait_seconds)
635
-
636
- # If there were already virtual IPs assigned to the adapter and already were set SkipAsSource,
637
- # we need to set SkipAsSource for them once again as well as for the new IPs.
638
- if set_virtual_ips_skip_as_source:
639
- wmi_standard_cimv2_instance, _ = wmi_helpers.get_wmi_instance(
640
- namespace='root\\StandardCimv2', wmi_instance=wmi_civ2_instance, locator=locator)
641
- msft_netipaddress.set_skip_as_source(ips_for_skip_as_source, enable=True, wmi_instance=wmi_standard_cimv2_instance)
642
- else:
643
- # print("[!] No new IPs to assign.")
644
- pass
627
+ # Enable DHCP + static IP coexistence on the interface.
628
+ process_complete: subprocess.CompletedProcess = netshw.enable_dhcp_static_coexistence(interface_name=interface_name)
629
+ if process_complete.returncode != 0:
630
+ print_api(f"[!] Failed to enable DHCP + static IP coexistence on interface {interface_name}.\n"
631
+ f" stdout: {process_complete.stdout}\n"
632
+ f" stderr: {process_complete.stderr}", color="red", logger=logger)
633
+ return None
634
+
635
+ for ip, mask in zip(ips_to_assign, masks_to_assign):
636
+ if verbose:
637
+ print_api(f"[+] Adding virtual IP {ip} with mask {mask} to interface {interface_name}.", logger=logger)
638
+
639
+ netshw.add_virtual_ip(
640
+ interface_name=interface_name,
641
+ ip=ip,
642
+ mask=mask,
643
+ skip_as_source=set_virtual_ips_skip_as_source
644
+ )
645
645
 
646
646
  if wait_until_applied:
647
647
  # Wait until the IP addresses are applied.
648
648
  for _ in range(wait_until_applied_seconds):
649
- current_ips = get_interface_ips(ipv4=True, ipv6=False, localhost=False, default_interface=True)
650
- if set(current_ips) == set(ips):
649
+ current_virtual_ips = get_interface_ips_powershell(interface_name=interface_name, ip_type="virtual")
650
+ if set(current_virtual_ips) == set(ips_to_assign):
651
651
  break
652
652
  time.sleep(1)
653
653
  else:
654
654
  raise TimeoutError("Timeout while waiting for the IP addresses to be applied.")
655
655
 
656
- return current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign
656
+ return ips_to_assign, masks_to_assign
657
+
658
+
659
+ def wait_for_ip_bindable_socket(
660
+ ip: str,
661
+ port: int = 0,
662
+ timeout: float = 15.0,
663
+ interval: float = 0.5,
664
+ ) -> None:
665
+ """
666
+ Wait until a single IP is bindable (or timeout).
667
+
668
+ Raises TimeoutError if the IP cannot be bound within 'timeout' seconds.
669
+ """
670
+
671
+ socket_base.wait_for_ip_bindable(ip=ip, port=port, timeout=timeout, interval=interval)
@@ -0,0 +1,84 @@
1
+ """Loading resources using stdlib importlib.resources APIs (Python 3.7+)
2
+ https://docs.python.org/3/library/importlib.html#module-importlib.resources"""
3
+ import importlib.resources
4
+ from contextlib import redirect_stdout
5
+ import io
6
+ import subprocess
7
+ import sys
8
+ from typing import Callable
9
+
10
+
11
+ class PackageMainsProcessor:
12
+ def __init__(
13
+ self,
14
+ script_file_stem: str = None
15
+ ):
16
+ self.script_file_stem: str = script_file_stem
17
+ self.resources_directory_name: str = 'a_mains'
18
+
19
+ def get_resource_path(self) -> str:
20
+ return f'{__package__}.{self.resources_directory_name}'
21
+
22
+ def read_script_file_to_string(self) -> str:
23
+ script_string = importlib.resources.read_text(self.get_resource_path(), f'{self.script_file_stem}.py')
24
+
25
+ return script_string
26
+
27
+ def execute_script_file(
28
+ self,
29
+ function_name: str = 'main',
30
+ args: tuple = None,
31
+ kwargs: dict = None,
32
+ get_printed_output: bool = False
33
+ ) -> str:
34
+ """
35
+ Execute a script file from the package resources and get result as string.
36
+
37
+ :param function_name: Name of the function to call within the script.
38
+ :param args: Tuple of positional arguments to pass to the function.
39
+ :param kwargs: Dictionary of keyword arguments to pass to the function.
40
+ :param get_printed_output: If True, captures and returns printed output instead of return value.
41
+
42
+ :return: Output of the script execution as a string.
43
+ """
44
+
45
+ if not args:
46
+ args = ()
47
+ if not kwargs:
48
+ kwargs = {}
49
+
50
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}" # script_file_name WITHOUT ".py"
51
+
52
+ module = importlib.import_module(module_name)
53
+ callable_function: Callable = getattr(module, function_name)
54
+
55
+ if get_printed_output:
56
+ with io.StringIO() as buffer, redirect_stdout(buffer):
57
+ callable_function(*args, **kwargs)
58
+ result = buffer.getvalue()
59
+ else:
60
+ result = callable_function(*args, **kwargs)
61
+
62
+ return result
63
+
64
+ def execute_script_with_subprocess(
65
+ self,
66
+ arguments: list = None
67
+ ) -> tuple[str, str, int]:
68
+ """
69
+ Execute a script file from the package resources using subprocess and get result as string.
70
+ :param arguments: Dictionary of arguments to pass to the script.
71
+ Example: ['--port', '8080', '-v']
72
+ :return: Tuple containing (stdout, stderr, returncode).
73
+ """
74
+
75
+ # script_file_name WITHOUT ".py"
76
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}"
77
+
78
+ command = [sys.executable, "-m", module_name]
79
+ if arguments:
80
+ command.extend(arguments)
81
+
82
+ result = subprocess.run(command, capture_output=True, text=True)
83
+
84
+ return result.stdout, result.stderr, result.returncode
@@ -2,6 +2,7 @@ import os
2
2
  import stat
3
3
  import contextlib
4
4
  import subprocess
5
+ import getpass
5
6
 
6
7
  # Import pwd only on linux.
7
8
  if os.name == 'posix':
@@ -20,6 +21,52 @@ def get_sudo_executer_username() -> str:
20
21
  return ''
21
22
 
22
23
 
24
+ def detect_current_user(
25
+ optional_env_user_var: str = 'CUSTOM_SCRIPTED_USER'
26
+ ) -> str:
27
+ """
28
+ Try to robustly determine the 'real' installing user.
29
+
30
+ Priority:
31
+ 1. FDB_INSTALL_USER env var (explicit override).
32
+ 2. If running as root with sudo: use SUDO_USER.
33
+ 3. Otherwise: use effective uid.
34
+ 4. Fallbacks: getpass.getuser() / $USER.
35
+
36
+ :param optional_env_user_var: str, name of the environment variable that can override the user detection.
37
+ :return: str, username.
38
+ """
39
+
40
+ # 1. Explicit override for weird environments (CI, containers, etc.)
41
+ env_user = os.getenv(optional_env_user_var)
42
+ if env_user:
43
+ return env_user
44
+
45
+ # 2. If we are root, prefer the sudo caller if any
46
+ try:
47
+ euid = os.geteuid()
48
+ except AttributeError: # non-POSIX, very unlikely here
49
+ euid = None
50
+
51
+ if euid == 0:
52
+ sudo_user = os.environ.get("SUDO_USER")
53
+ if sudo_user:
54
+ return sudo_user
55
+
56
+ # 3. Normal case: effective uid -> username
57
+ if euid is not None:
58
+ try:
59
+ return pwd.getpwuid(euid).pw_name
60
+ except Exception:
61
+ pass
62
+
63
+ # 4. Fallbacks that don’t depend on utmp/tty
64
+ try:
65
+ return getpass.getuser()
66
+ except Exception:
67
+ return os.environ.get("USER", "unknown")
68
+
69
+
23
70
  def set_executable(file_path: str):
24
71
  """
25
72
  Function sets the executable permission on a file.
atomicshop/print_api.py CHANGED
@@ -1,13 +1,14 @@
1
1
  import sys
2
2
  import logging
3
+ from typing import Any
3
4
 
4
5
  from .basics import ansi_escape_codes
5
6
  from .basics import tracebacks
6
7
 
7
8
 
8
9
  def print_api(
9
- message: any,
10
- color: any = None,
10
+ message: Any,
11
+ color: Any = None,
11
12
  print_end: str = '\n',
12
13
  rtl: bool = False,
13
14
  error_type: bool = False,
@@ -73,7 +74,6 @@ def print_api(
73
74
 
74
75
  # Inner functions already get all the local variables of the main function.
75
76
  def print_or_logger():
76
- from .wrappers.loggingw import loggingw
77
77
  nonlocal message
78
78
  nonlocal color
79
79
  nonlocal traceback_string
@@ -83,8 +83,6 @@ def print_api(
83
83
 
84
84
  # If 'rtl' is set to 'True', we'll add Right-To-Left text conversion to 'message'.
85
85
  if rtl:
86
- # Lazy importing of 'bidi' library. It's not a problem since python caches the library after first import.
87
- # Off-course, it will be imported from the cache each time this section is triggered.
88
86
  # pip install python-bidi
89
87
  from bidi.algorithm import get_display
90
88
  message = get_display(message)