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

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

Potentially problematic release.


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

Files changed (120) 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 +22 -44
  12. atomicshop/mitm/connection_thread_worker.py +383 -165
  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 +91 -89
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +238 -122
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/mitm/statistic_analyzer.py +33 -12
  24. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
  25. atomicshop/networks.py +160 -92
  26. atomicshop/package_mains_processor.py +84 -0
  27. atomicshop/permissions/ubuntu_permissions.py +47 -0
  28. atomicshop/print_api.py +3 -5
  29. atomicshop/process.py +11 -4
  30. atomicshop/python_functions.py +23 -108
  31. atomicshop/speech_recognize.py +8 -0
  32. atomicshop/ssh_remote.py +140 -164
  33. atomicshop/web.py +63 -22
  34. atomicshop/web_apis/google_llm.py +22 -14
  35. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  36. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  37. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  38. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  39. atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
  40. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  41. atomicshop/wrappers/githubw.py +180 -68
  42. atomicshop/wrappers/loggingw/consts.py +1 -1
  43. atomicshop/wrappers/loggingw/handlers.py +1 -1
  44. atomicshop/wrappers/loggingw/loggingw.py +20 -4
  45. atomicshop/wrappers/loggingw/reading.py +18 -0
  46. atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
  47. atomicshop/wrappers/netshw.py +124 -3
  48. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  49. atomicshop/wrappers/powershell_networking.py +80 -0
  50. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  51. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  52. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  53. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  54. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  55. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  56. atomicshop/wrappers/socketw/certificator.py +19 -9
  57. atomicshop/wrappers/socketw/creator.py +101 -14
  58. atomicshop/wrappers/socketw/dns_server.py +17 -5
  59. atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
  60. atomicshop/wrappers/socketw/process_getter.py +86 -0
  61. atomicshop/wrappers/socketw/receiver.py +29 -9
  62. atomicshop/wrappers/socketw/sender.py +10 -9
  63. atomicshop/wrappers/socketw/sni.py +31 -10
  64. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  65. atomicshop/wrappers/socketw/socket_client.py +11 -10
  66. atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
  67. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  68. atomicshop/wrappers/ubuntu_terminal.py +21 -18
  69. atomicshop/wrappers/win_auditw.py +189 -0
  70. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  71. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
  72. atomicshop/_basics_temp.py +0 -101
  73. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  74. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  75. atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
  76. atomicshop/a_installs/ubuntu/mongodb.py +0 -12
  77. atomicshop/a_installs/win/fibratus.py +0 -9
  78. atomicshop/a_installs/win/mongodb.py +0 -9
  79. atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
  80. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  81. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  82. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  83. atomicshop/addons/package_setup/Setup.cmd +0 -7
  84. atomicshop/archiver/__init__.py +0 -0
  85. atomicshop/archiver/_search_in_zip.py +0 -189
  86. atomicshop/archiver/search_in_archive.py +0 -284
  87. atomicshop/archiver/sevenz_app_w.py +0 -86
  88. atomicshop/archiver/sevenzs.py +0 -73
  89. atomicshop/archiver/shutils.py +0 -34
  90. atomicshop/archiver/zips.py +0 -353
  91. atomicshop/file_types.py +0 -24
  92. atomicshop/pbtkmultifile_argparse.py +0 -88
  93. atomicshop/script_as_string_processor.py +0 -42
  94. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  95. atomicshop/ssh_scripts/process_from_port.py +0 -27
  96. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  97. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  98. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  99. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
  100. atomicshop/wrappers/ffmpegw.py +0 -125
  101. atomicshop/wrappers/fibratusw/__init__.py +0 -0
  102. atomicshop/wrappers/fibratusw/install.py +0 -80
  103. atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
  104. atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
  105. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  106. atomicshop/wrappers/socketw/get_process.py +0 -123
  107. atomicshop/wrappers/wslw.py +0 -192
  108. atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
  109. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  110. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  111. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  112. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  113. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  114. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  115. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  116. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  117. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  118. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  119. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  120. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from typing import Union
4
4
  from .print_api import print_api
5
5
 
6
6
 
7
- def get_current_python_version_string() -> str:
7
+ def get_python_version_string() -> str:
8
8
  """
9
9
  Function gets version MAJOR.MINOR.MICRO from 'sys.version_info' object and returns it as a string.
10
10
  :return: python MAJOR.MINOR.MICRO version string.
@@ -13,120 +13,35 @@ def get_current_python_version_string() -> str:
13
13
  return f'{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}'
14
14
 
15
15
 
16
- # noinspection PyUnusedLocal
17
- def check_if_version_object_is_tuple_or_string(version_object: any,
18
- **kwargs) -> tuple:
19
- """
20
- Function checks if 'version_object' that was passed is tuple or string.
21
- If it's tuple then returns it back. If it's string, converts to tuple then returns it.
22
- If the object is none of the above, returns None.
23
-
24
- :param version_object: Can be string ('3.10') or tuple of integers ((3, 10)).
25
- :return:
26
- """
27
- # Check if tuple was passed.
28
- if isinstance(version_object, tuple):
29
- return version_object
30
- else:
31
- # Then check if a string was passed.
32
- if isinstance(version_object, str):
33
- # The check will be against tuple of integers, so we'll convert a string to tuple of integers.
34
- return tuple(map(int, version_object.split('.')))
35
- else:
36
- message = f'[*] Function: [check_if_version_object_is_tuple_or_string]\n' \
37
- f'[*] [version_object] object passed is not tuple or string.\n' \
38
- f'[*] Object type: {type(version_object)}\n' \
39
- f'[*] Object content {version_object}\n' \
40
- f'Exiting...'
41
- print_api(message, error_type=True, logger_method='critical', **kwargs)
42
-
43
- return None
44
-
45
-
46
- # noinspection PyUnusedLocal
47
16
  def check_python_version_compliance(
48
- minimum_version: Union[str, tuple] = None,
49
- maximum_version: Union[str, tuple] = None,
50
- minor_version: Union[str, tuple] = None,
51
- **kwargs
52
- ) -> bool:
17
+ min_ver: tuple = None,
18
+ max_ver: tuple = None,
19
+ ) -> str | None:
53
20
  """
54
21
  Python version check. Should be executed before importing external libraries, since they depend on Python version.
55
22
 
56
- :param minimum_version: Can be string ('3.10') or tuple of integers ((3, 10)).
57
- :param maximum_version: Can be string ('3.10') or tuple of integers ((3, 10)).
23
+ :param min_ver: tuple of integers (3, 10).
24
+ :param max_ver: tuple of integers (3, 10).
58
25
  If maximum version is not specified, it will be considered as all versions above the minimum are compliant.
59
- :param minor_version: Can be string ('3.10') or tuple of integers ((3, 10)).
60
- If minor version is specified, it will be considered as all the versions with the same major, minor version
61
- are compliant. Example: if minor_version is '3.10', then all the versions with '3.10.x' are compliant.
62
- :return:
26
+ :return: If version is not compliant, returns string with error message. Otherwise, returns None.
63
27
  """
64
28
 
65
- if not minimum_version and not maximum_version and not minor_version:
29
+ if not min_ver and not max_ver:
66
30
  raise ValueError("At least one of the version parameters should be passed.")
67
31
 
68
- if minor_version and (minimum_version or maximum_version):
69
- raise ValueError("Minor version should be passed alone.")
70
-
71
- # Check objects for string or tuple and get the tuple.
72
- if minimum_version:
73
- minimum_version_scheme: tuple = check_if_version_object_is_tuple_or_string(minimum_version, **kwargs)
74
- else:
75
- minimum_version_scheme = tuple()
76
- # If 'maximum_version' object was passed, check it for string or tuple and get the tuple.
77
- if maximum_version:
78
- maximum_version_scheme: tuple = check_if_version_object_is_tuple_or_string(maximum_version, **kwargs)
79
- else:
80
- maximum_version_scheme = tuple()
81
-
82
- # If 'minor_version' object was passed, check it for string or tuple and get the tuple.
83
- if minor_version:
84
- minor_version_scheme: tuple = check_if_version_object_is_tuple_or_string(minor_version, **kwargs)
32
+ current_version_info: tuple = sys.version_info[:3]
33
+ if min_ver and not max_ver:
34
+ if current_version_info < min_ver:
35
+ return f'Python version {".".join(map(str, min_ver))} or higher is required. '\
36
+ f'Current version is {".".join(map(str, current_version_info))}.'
37
+ elif max_ver and not min_ver:
38
+ if current_version_info > max_ver:
39
+ return f'Python version up to {".".join(map(str, max_ver))} is required. '\
40
+ f'Current version is {".".join(map(str, current_version_info))}.'
41
+ elif min_ver and max_ver:
42
+ if not (min_ver <= current_version_info <= max_ver):
43
+ return f'Python version between {".".join(map(str, min_ver))} and '\
44
+ f'{".".join(map(str, max_ver))} is required. '\
45
+ f'Current version is {".".join(map(str, current_version_info))}.'
85
46
  else:
86
- minor_version_scheme = tuple()
87
-
88
- # Get current python version.
89
- python_version_full: str = get_current_python_version_string()
90
-
91
- message = f"[*] Current Python Version: {python_version_full}"
92
- print_api(message, logger_method='info', **kwargs)
93
-
94
- if minor_version_scheme:
95
- # Check if current python version is later or equals to the minimum required version.
96
- if sys.version_info[:2] != minor_version_scheme:
97
- message = f"[!!!] YOU NEED TO INSTALL ANY PYTHON " \
98
- f"[{'.'.join(str(i) for i in minor_version_scheme)}] version, " \
99
- f"to work properly."
100
- print_api(message, error_type=True, logger_method='critical', **kwargs)
101
-
102
- return False
103
- elif minimum_version_scheme and maximum_version_scheme:
104
- if not sys.version_info >= minimum_version_scheme or not sys.version_info < maximum_version_scheme:
105
- message = f"[!!!] YOU NEED TO INSTALL AT LEAST PYTHON " \
106
- f"[{'.'.join(str(i) for i in minimum_version_scheme)}], " \
107
- f"AND EARLIER THAN [{'.'.join(str(i) for i in maximum_version_scheme)}], " \
108
- f"to work properly."
109
- print_api(message, error_type=True, logger_method='critical', **kwargs)
110
-
111
- return False
112
- elif minimum_version_scheme and not maximum_version_scheme:
113
- if not sys.version_info >= minimum_version_scheme:
114
- message = f"[!!!] YOU NEED TO INSTALL AT LEAST PYTHON " \
115
- f"[{'.'.join(str(i) for i in minimum_version_scheme)}], " \
116
- f"to work properly."
117
- print_api(message, error_type=True, logger_method='critical', **kwargs)
118
-
119
- return False
120
- elif not minimum_version_scheme and maximum_version_scheme:
121
- if not sys.version_info < maximum_version_scheme:
122
- message = f"[!!!] YOU NEED TO INSTALL EARLIER THAN PYTHON " \
123
- f"[{'.'.join(str(i) for i in maximum_version_scheme)}], " \
124
- f"to work properly."
125
- print_api(message, error_type=True, logger_method='critical', **kwargs)
126
-
127
- return False
128
-
129
- message = "[*] Version Check PASSED."
130
- print_api(message, logger_method='info', **kwargs)
131
-
132
- return True
47
+ return None
@@ -1,3 +1,11 @@
1
+ # TODO: Change manual wrapper to:
2
+ # from ffmpy import FFmpeg
3
+ # ff = FFmpeg(
4
+ # inputs={'input.mp4': None},
5
+ # outputs={'output.avi': None}
6
+ # )
7
+ # ff.run()
8
+
1
9
  from .wrappers.ffmpegw import FFmpegWrapper
2
10
  from .tempfiles import TempFile
3
11
  from .web import download
atomicshop/ssh_remote.py CHANGED
@@ -1,8 +1,7 @@
1
1
  import sys
2
- import base64
3
- import socket
4
2
  import logging
5
3
  from pathlib import Path
4
+ import shlex
6
5
 
7
6
  try:
8
7
  import paramiko
@@ -12,7 +11,12 @@ except ImportError as exception_object:
12
11
 
13
12
  from .print_api import print_api
14
13
  from .wrappers.loggingw import loggingw
15
- from .wrappers.socketw import base
14
+ from .wrappers.socketw import socket_base
15
+
16
+
17
+ class SSHRemoteWrapperNoPythonFound(Exception):
18
+ """Raised when no usable Python 3 interpreter found on remote host."""
19
+ pass
16
20
 
17
21
 
18
22
  class SSHRemote:
@@ -101,17 +105,23 @@ class SSHRemote:
101
105
  # Initializing paramiko SSHClient class
102
106
  self.ssh_client = paramiko.SSHClient()
103
107
 
108
+ # Variable to store detected python command on remote (python3 / python).
109
+ self.python_cmd: str | None = None
110
+
104
111
  if logger:
105
112
  # Create child logger for the provided logger with the module's name.
106
113
  self.logger: logging.Logger = loggingw.get_logger_with_level(f'{logger.name}.{Path(__file__).stem}')
107
114
  else:
108
115
  self.logger: logging.Logger = logger
109
116
 
110
- def connect(self):
117
+ def connect(
118
+ self,
119
+ timeout: int = 60
120
+ ):
111
121
  error: str = str()
112
122
 
113
123
  # Get all local interfaces IPv4 addresses.
114
- local_interfaces_ipv4 = base.get_local_network_interfaces_ip_address("ipv4", True)
124
+ local_interfaces_ipv4 = socket_base.get_local_network_interfaces_ip_address("ipv4", True)
115
125
  # Check if the target IP address is in the list of local interfaces.
116
126
  if self.ip_address in local_interfaces_ipv4:
117
127
  # If it is, we don't need to connect to it via SSH, it means that we want to connect to ourselves.
@@ -129,75 +139,15 @@ class SSHRemote:
129
139
  # with description of
130
140
  # Server 'address_goes_here' not found in known_hosts
131
141
  self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
132
- try:
133
- # Executing SSH connection to client.
134
- self.ssh_client.connect(self.ip_address, username=self.username, password=self.password, timeout=60)
135
- # When port 22 is unreachable on the client.
136
- except paramiko.ssh_exception.NoValidConnectionsError as e:
137
- error = str(e)
138
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
139
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True)
140
- pass
141
- except paramiko.ssh_exception.SSHException as e:
142
- error = str(e)
143
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
144
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True)
145
- pass
146
- except ConnectionResetError:
147
- # Returning the error.
148
- error = "An existing connection was forcibly closed by the remote host."
149
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
150
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True)
151
- pass
152
- except TimeoutError:
153
- # Returning the error.
154
- error = "Connection timed out."
155
- # Logging the error also. Since the process name isn't critical, we'll continue script execution.
156
- print_api(error, logger=self.logger, logger_method='error', traceback_string=True)
157
- pass
142
+
143
+ # Executing SSH connection to client.
144
+ self.ssh_client.connect(self.ip_address, username=self.username, password=self.password, timeout=timeout)
158
145
 
159
146
  return error
160
147
 
161
148
  def close(self):
162
149
  self.ssh_client.close()
163
150
 
164
- def exec_command_with_error_handling(self, script_string: str):
165
- # Defining variables.
166
- stdin = None
167
- stdout = None
168
- stderr = None
169
- result_exception = None
170
-
171
- # Don't put debugging break point over the next line in PyCharm. For some reason it gets stuck.
172
- # Put the point right after that.
173
- try:
174
- stdin, stdout, stderr = self.ssh_client.exec_command(command=script_string, timeout=30)
175
- except AttributeError as function_exception_object:
176
- if function_exception_object.name == "open_session":
177
- result_exception = "'SSHRemote().connect' wasn't executed."
178
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True)
179
-
180
- # Since getting Process name is not the main feature of the server, we can pass the exception
181
- pass
182
- else:
183
- result_exception = f"Couldn't execute script over SSH. Unknown yet exception with 'AttributeError' " \
184
- f"and name: {function_exception_object.name}"
185
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True)
186
- # Since getting Process name is not the main feature of the server, we can pass the exception
187
- pass
188
- except socket.error:
189
- result_exception = "Couldn't execute script over SSH. SSH socket closed."
190
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True)
191
- # Since getting Process name is not the main feature of the server, we can pass the exception
192
- pass
193
- except Exception:
194
- result_exception = "Couldn't execute script over SSH. Unknown yet exception."
195
- print_api(result_exception, logger=self.logger, logger_method='error', traceback_string=True)
196
- # Since getting Process name is not the main feature of the server, we can pass the exception
197
- pass
198
-
199
- return stdin, stdout, stderr, result_exception
200
-
201
151
  @staticmethod
202
152
  def check_console_output_for_errors(console_output_string: str):
203
153
  # Defining variables.
@@ -212,104 +162,132 @@ class SSHRemote:
212
162
  if "ModuleNotFoundError: No module named" in line:
213
163
  function_result = f"Python library is not installed - {line}"
214
164
  break
165
+ else:
166
+ function_result = console_output_string
215
167
 
216
168
  return function_result
217
169
 
218
- def remote_execution(self, script_string: str):
219
- # Defining variables.
220
- output_lines = None
221
- function_error = None
222
- stdin = None
223
- stdout = None
224
- stderr = None
225
- exec_exception = None
170
+ def remote_execution(
171
+ self,
172
+ command: str,
173
+ script_string: str = None
174
+ ) -> tuple[str, str]:
175
+ """
176
+ Function to execute any command over SSH.
177
+
178
+ :param command: command to execute over SSH.
179
+ :param script_string: string representation of script to execute as input.
180
+ Example:
181
+ command = "python - 56734"
182
+ script_string = "import sys;print(f'sys.argv[0]')"
183
+
184
+ Under ssh in terminal it would execute like this:
185
+ ssh User@HostIpv4
186
+
187
+ python - 56734 << EOF
188
+ import sys;print(f'sys.argv[0]')
189
+ EOF
190
+
191
+ Or as onliner:
192
+ ssh User@HostIpv4 "python - 56734 << EOF import sys;print(f'sys.argv[0]') EOF"
193
+
194
+ or using a specific file path:
195
+ ssh User@HostIpv4 "python - 56734" < /path/to/script.py
196
+
197
+ :return: SSH console output, Error output
198
+ """
199
+ output_result: str = str()
200
+ error_result: str = str()
226
201
 
227
202
  # Execute the command over SSH remotely.
228
- stdin, stdout, stderr, exec_exception = self.exec_command_with_error_handling(script_string)
229
- # If exception was returned from execution.
230
- if exec_exception:
231
- self.logger.info("Trying to reconnect over SSH.")
232
- # Close existing SSH Connection.
233
- self.close()
234
- # And connect again.
235
- self.connect()
236
- self.logger.info("Reconnected. Trying to send the command one more time.")
237
- # Try to execute the command over SSH remotely again.
238
- stdin, stdout, stderr, exec_exception = self.exec_command_with_error_handling(script_string)
239
- # If there was an exception again.
240
- if exec_exception:
241
- # Populate the function_error variable that will be returned outside.
242
- function_error = exec_exception
243
-
244
- # If there was no exception executing the remote command.
245
- if not function_error:
246
- # Reading the buffer of stdout.
247
- output_lines = stdout.readlines()
248
- # Reading the buffer of stderr.
249
- function_error = stderr.readlines()
250
-
251
- # Joining error lines list to string if not empty.
252
- if function_error:
253
- function_error = ''.join(function_error)
254
- # Else, joining output lines to string.
255
- else:
256
- output_lines = ''.join(output_lines)
203
+ stdin, stdout, stderr = self.ssh_client.exec_command(command=command, timeout=30)
204
+
205
+ # Writing the script string into stdin buffer.
206
+ if script_string:
207
+ stdin.write(script_string)
208
+ stdin.channel.shutdown_write()
209
+
210
+ # Reading the buffer of stdout.
211
+ output_lines: list = stdout.readlines()
212
+ # Reading the buffer of stderr.
213
+ error_lines: list = stderr.readlines()
214
+
215
+ # Joining error lines list to string if not empty.
216
+ if error_lines:
217
+ error_result: str = ''.join(error_lines)
218
+ # Else, joining output lines to string.
219
+ else:
220
+ output_result = ''.join(output_lines)
257
221
 
258
- # Since they're "file-like" objects we need to close them after we finished using.
259
- stdin.close()
260
- stdout.close()
261
- stderr.close()
222
+ # Since they're "file-like" objects we need to close them after we finished using.
223
+ stdin.close()
224
+ stdout.close()
225
+ stderr.close()
262
226
 
263
- return output_lines, function_error
227
+ return output_result, error_result
264
228
 
265
- def remote_execution_python(self, script_string: str):
229
+ def _detect_remote_python_cmd_name(self) -> str:
266
230
  """
267
- Function to execute python script over SSH.
231
+ Try 'python3' then 'python' on the remote, return the one that is Python 3.
232
+ Raises if neither works.
233
+ """
234
+ for candidate in ("python3", "python"):
235
+ # Use a simple version check that works on both Windows and Linux
236
+ cmd = f'{candidate} -c "import sys; print(sys.version_info[0])"'
237
+ stdin, stdout, stderr = self.ssh_client.exec_command(cmd, timeout=5)
238
+
239
+ out = stdout.read().decode().strip()
240
+ exit_status = stdout.channel.recv_exit_status()
241
+
242
+ if exit_status == 0 and out == "3":
243
+ print_api(f"Detected remote Python 3 interpreter (once per client port): {candidate}", logger=self.logger)
244
+ return candidate
268
245
 
269
- Example:
270
- network.logger.info("Initializing SSH connection to get the calling process.")
271
-
272
- # Initializing SSHRemote class.
273
- ssh_client = SSHRemote(ip_address=client_message.client_ip, username=username, password=password)
274
- # Making actual SSH Connection to the computer.
275
- ssh_connection_error = ssh_client.connect()
276
- # If there's an exception / error during connection.
277
- if ssh_connection_error:
278
- # Put the error in the process name value.
279
- client_message.process_name = ssh_connection_error
280
- else:
281
- # If no error, then initialize the variables for python script execution over SSH.
282
- remote_output = remote_error = None
283
-
284
- # Put source port variable inside the string script.
285
- script_string: str = \
286
- put_variable_into_string_script(ssh_script_port_by_process_string, client_message.source_port)
287
-
288
- # Execute the python script on remote computer over SSH.
289
- remote_output, remote_error = ssh_client.remote_execution_python(script_string)
290
-
291
- # If there was an error during execution, put it in process name.
292
- if remote_error:
293
- client_message.process_name = remote_error
294
- # If there was no error during execution, put the output of the ssh to process name.
295
- else:
296
- client_message.process_name = remote_output
297
- network.logger.info(f"Remote SSH: Client executing Command Line: {client_message.process_name}")
246
+ raise SSHRemoteWrapperNoPythonFound("No usable Python 3 interpreter found on remote host")
247
+
248
+ def _get_python_cmd(self) -> str:
249
+ if self.python_cmd is None:
250
+ self.python_cmd = self._detect_remote_python_cmd_name()
251
+ return self.python_cmd
252
+
253
+ def remote_execution_python(
254
+ self,
255
+ script_string: str,
256
+ script_arg_values: tuple = None,
257
+ script_kwargs: dict = None,
258
+ ):
259
+ """
260
+ Function to execute python script over SSH.
298
261
 
299
262
  :param script_string: string representation of python script.
263
+ :param script_arg_values: values arguments to pass to the script. Example for first argument: 56734
264
+ :param script_kwargs: keyword arguments to pass to the script.
265
+ Example: {'-r': None}
266
+ Interpreted as: -r
267
+ Example: {'-f': 'value'}
268
+ Interpreted as: -f value
269
+ Example: {'--arg': value}
270
+ Interpreted as: --arg value
271
+
300
272
  :return: SSH console output, Error output
301
273
  """
302
274
  # Defining variables.
303
- function_return = None
304
- function_error = None
275
+ error_result: str | None = None
305
276
 
306
- encoded_base64_string = base64.b64encode(script_string.encode('ascii'))
307
- command_string: str = fr'python -c "import base64;exec(base64.b64decode({encoded_base64_string}))"'
277
+ python_cmd = self._get_python_cmd()
278
+ command: str = f"{python_cmd} -"
308
279
 
309
- # remote_output, remote_error = ssh_client.remote_execution('ipconfig')
310
- # remote_output, remote_error = ssh_client.remote_execution("python -c print('Hello')")
311
- # remote_output, remote_error = ssh_client.remote_execution("python -c import psutil")
312
- remote_output, remote_error = self.remote_execution(command_string)
280
+ if script_arg_values:
281
+ for arg in script_arg_values:
282
+ command += " " + shlex.quote(str(arg))
283
+
284
+ if script_kwargs:
285
+ for key, value in script_kwargs.items():
286
+ command += f" {shlex.quote(str(key))}"
287
+ if value is not None:
288
+ command += " " + shlex.quote(str(value))
289
+
290
+ remote_output, remote_error = self.remote_execution(command=command, script_string=script_string)
313
291
 
314
292
  # If there was an error during remote execution
315
293
  if remote_error:
@@ -318,37 +296,35 @@ class SSHRemote:
318
296
  # If the message is known and didn't return empty.
319
297
  if console_check:
320
298
  # 'execution_error' variable will be that full error.
321
- function_error = console_check
299
+ error_result = console_check
300
+ else:
301
+ error_result = remote_error
322
302
 
323
- return remote_output, function_error
303
+ return remote_output, error_result
324
304
 
325
- def connect_get_client_commandline(self, script_string):
305
+ def connect_get_client_commandline(
306
+ self,
307
+ port: int,
308
+ script_string: str):
326
309
  # Defining locals.
327
- execution_output = None
328
- execution_error = None
310
+ execution_output: str | None = None
329
311
 
330
312
  # Making actual SSH Connection to the computer.
331
313
  execution_error = self.connect()
332
314
  # if there was an error, try to connect again.
333
315
  if execution_error:
334
- self.logger.info("Retrying SSH Connection Initialization.")
316
+ print_api("Retrying SSH Connection Initialization.", logger=self.logger, logger_method='info')
335
317
  execution_error = self.connect()
336
318
 
337
319
  # If there was still an error, we won't be executing the script. And the error will be passed to
338
320
  # 'process_name'.
339
321
  if not execution_error:
340
- self.logger.info("Executing SSH command to acquire the calling process.")
322
+ print_api("Executing SSH command to acquire the calling process.", logger=self.logger, logger_method='info')
341
323
 
342
- try:
343
- execution_output, execution_error = self.remote_execution_python(script_string)
344
- # Basically we don't care much about SSH exceptions. Just log them and pass to record.
345
- except Exception as function_exception_object:
346
- execution_error = function_exception_object
347
- print_api(execution_error, logger=self.logger, logger_method='error', traceback_string=True)
348
- pass
324
+ execution_output, execution_error = self.remote_execution_python(script_string=script_string, script_arg_values=(str(port),))
349
325
 
350
326
  # Closing SSH connection at this stage.
351
327
  self.close()
352
- self.logger.info("Acquired. Closed SSH connection.")
328
+ print_api("Acquired. Closed SSH connection.", logger=self.logger, logger_method='info')
353
329
 
354
330
  return execution_output, execution_error