atomicshop 3.3.8__py3-none-any.whl → 3.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/basics/strings.py +1 -1
- atomicshop/certificates.py +2 -2
- atomicshop/dns.py +26 -28
- atomicshop/etws/traces/trace_tcp.py +1 -2
- atomicshop/mitm/centered_settings.py +133 -0
- atomicshop/mitm/config_static.py +22 -44
- atomicshop/mitm/connection_thread_worker.py +383 -165
- atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
- atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
- atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
- atomicshop/mitm/engines/create_module_template.py +1 -2
- atomicshop/mitm/import_config.py +91 -89
- atomicshop/mitm/initialize_engines.py +1 -2
- atomicshop/mitm/message.py +5 -4
- atomicshop/mitm/mitm_main.py +238 -122
- atomicshop/mitm/recs_files.py +61 -5
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +33 -12
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
- atomicshop/networks.py +160 -92
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/ubuntu_permissions.py +47 -0
- atomicshop/print_api.py +3 -5
- atomicshop/process.py +11 -4
- atomicshop/python_functions.py +23 -108
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +140 -164
- atomicshop/web.py +63 -22
- atomicshop/web_apis/google_llm.py +22 -14
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
- atomicshop/wrappers/dockerw/dockerw.py +2 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
- atomicshop/wrappers/githubw.py +180 -68
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/handlers.py +1 -1
- atomicshop/wrappers/loggingw/loggingw.py +20 -4
- atomicshop/wrappers/loggingw/reading.py +18 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
- atomicshop/wrappers/netshw.py +124 -3
- atomicshop/wrappers/playwrightw/scenarios.py +1 -1
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
- atomicshop/wrappers/socketw/certificator.py +19 -9
- atomicshop/wrappers/socketw/creator.py +101 -14
- atomicshop/wrappers/socketw/dns_server.py +17 -5
- atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +29 -9
- atomicshop/wrappers/socketw/sender.py +10 -9
- atomicshop/wrappers/socketw/sni.py +31 -10
- atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
- atomicshop/wrappers/socketw/socket_client.py +11 -10
- atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
- atomicshop/wrappers/socketw/ssl_base.py +6 -2
- atomicshop/wrappers/ubuntu_terminal.py +21 -18
- atomicshop/wrappers/win_auditw.py +189 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
- atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
- atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
- atomicshop/a_installs/ubuntu/mongodb.py +0 -12
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/__init__.py +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/search_in_archive.py +0 -284
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -73
- atomicshop/archiver/shutils.py +0 -34
- atomicshop/archiver/zips.py +0 -353
- atomicshop/file_types.py +0 -24
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/script_as_string_processor.py +0 -42
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -449
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/__init__.py +0 -0
- atomicshop/wrappers/fibratusw/install.py +0 -80
- atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
- atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/get_process.py +0 -123
- atomicshop/wrappers/wslw.py +0 -192
- atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
atomicshop/python_functions.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Union
|
|
|
4
4
|
from .print_api import print_api
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
57
|
-
:param
|
|
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
|
-
:
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
atomicshop/speech_recognize.py
CHANGED
|
@@ -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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
227
|
+
return output_result, error_result
|
|
264
228
|
|
|
265
|
-
def
|
|
229
|
+
def _detect_remote_python_cmd_name(self) -> str:
|
|
266
230
|
"""
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
304
|
-
function_error = None
|
|
275
|
+
error_result: str | None = None
|
|
305
276
|
|
|
306
|
-
|
|
307
|
-
|
|
277
|
+
python_cmd = self._get_python_cmd()
|
|
278
|
+
command: str = f"{python_cmd} -"
|
|
308
279
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
299
|
+
error_result = console_check
|
|
300
|
+
else:
|
|
301
|
+
error_result = remote_error
|
|
322
302
|
|
|
323
|
-
return remote_output,
|
|
303
|
+
return remote_output, error_result
|
|
324
304
|
|
|
325
|
-
def connect_get_client_commandline(
|
|
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
|
-
|
|
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
|
-
|
|
322
|
+
print_api("Executing SSH command to acquire the calling process.", logger=self.logger, logger_method='info')
|
|
341
323
|
|
|
342
|
-
|
|
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
|
-
|
|
328
|
+
print_api("Acquired. Closed SSH connection.", logger=self.logger, logger_method='info')
|
|
353
329
|
|
|
354
330
|
return execution_output, execution_error
|