atomicshop 2.15.13__py3-none-any.whl → 2.16.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/dns_gateway_setting.py +11 -0
- atomicshop/basics/booleans.py +14 -5
- atomicshop/dns.py +104 -0
- atomicshop/file_io/docxs.py +8 -0
- atomicshop/file_io/tomls.py +133 -0
- atomicshop/filesystem.py +5 -4
- atomicshop/get_process_list.py +3 -3
- atomicshop/mitm/config_static.py +195 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +54 -90
- atomicshop/mitm/import_config.py +147 -139
- atomicshop/mitm/initialize_engines.py +7 -2
- atomicshop/mitm/initialize_mitm_server.py +161 -107
- atomicshop/mitm/shared_functions.py +0 -1
- atomicshop/mitm/statistic_analyzer.py +13 -1
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +54 -14
- atomicshop/script_as_string_processor.py +5 -1
- atomicshop/wrappers/cryptographyw.py +3 -3
- atomicshop/wrappers/psutilw/networks.py +25 -1
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/wmis/helpers.py +127 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +167 -0
- atomicshop/wrappers/socketw/accepter.py +8 -8
- atomicshop/wrappers/socketw/base.py +13 -0
- atomicshop/wrappers/socketw/certificator.py +202 -149
- atomicshop/wrappers/socketw/creator.py +15 -35
- atomicshop/wrappers/socketw/dns_server.py +155 -102
- atomicshop/wrappers/socketw/exception_wrapper.py +8 -27
- atomicshop/wrappers/socketw/get_process.py +115 -95
- atomicshop/wrappers/socketw/sni.py +298 -164
- atomicshop/wrappers/socketw/socket_client.py +5 -12
- atomicshop/wrappers/socketw/socket_server_tester.py +1 -1
- atomicshop/wrappers/socketw/socket_wrapper.py +328 -72
- atomicshop/wrappers/socketw/statistics_csv.py +94 -16
- {atomicshop-2.15.13.dist-info → atomicshop-2.16.0.dist-info}/METADATA +1 -1
- {atomicshop-2.15.13.dist-info → atomicshop-2.16.0.dist-info}/RECORD +41 -36
- atomicshop/mitm/config_editor.py +0 -37
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.13.dist-info → atomicshop-2.16.0.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.15.13.dist-info → atomicshop-2.16.0.dist-info}/WHEEL +0 -0
- {atomicshop-2.15.13.dist-info → atomicshop-2.16.0.dist-info}/top_level.txt +0 -0
|
@@ -1,34 +1,180 @@
|
|
|
1
1
|
import threading
|
|
2
2
|
import select
|
|
3
|
+
from typing import Literal, Union
|
|
3
4
|
|
|
4
|
-
from
|
|
5
|
+
from ..psutilw import networks
|
|
5
6
|
from ...script_as_string_processor import ScriptAsStringProcessor
|
|
6
|
-
from ... import queues
|
|
7
|
+
from ... import queues, filesystem
|
|
8
|
+
from ...basics import booleans
|
|
7
9
|
from ...print_api import print_api
|
|
8
10
|
|
|
11
|
+
from . import base, creator, get_process, accepter, statistics_csv, ssl_base, sni
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SocketWrapperPortInUseError(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SocketWrapperConfigurationValuesError(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
9
21
|
|
|
10
22
|
SNI_QUEUE = queues.NonBlockQueue()
|
|
11
23
|
|
|
12
24
|
|
|
13
|
-
# === Socket Wrapper ===================================================================================================
|
|
14
25
|
class SocketWrapper:
|
|
15
26
|
def __init__(
|
|
16
27
|
self,
|
|
28
|
+
listening_interface: str,
|
|
29
|
+
listening_port_list: list[int],
|
|
30
|
+
forwarding_dns_service_ipv4_list___only_for_localhost: list = None,
|
|
31
|
+
ca_certificate_name: str = None,
|
|
32
|
+
ca_certificate_filepath: str = None,
|
|
33
|
+
default_server_certificate_usage: bool = False,
|
|
34
|
+
default_server_certificate_name: str = None,
|
|
35
|
+
default_certificate_domain_list: list = None,
|
|
36
|
+
default_server_certificate_directory: str = None,
|
|
37
|
+
sni_custom_callback_function: callable = None,
|
|
38
|
+
sni_use_default_callback_function: bool = False,
|
|
39
|
+
sni_use_default_callback_function_extended: bool = False,
|
|
40
|
+
sni_add_new_domains_to_default_server_certificate: bool = False,
|
|
41
|
+
sni_create_server_certificate_for_each_domain: bool = False,
|
|
42
|
+
sni_server_certificates_cache_directory: str = None,
|
|
43
|
+
sni_get_server_certificate_from_server_socket: bool = False,
|
|
44
|
+
sni_server_certificate_from_server_socket_download_directory: str = None,
|
|
45
|
+
skip_extension_id_list: list = None,
|
|
46
|
+
custom_server_certificate_usage: bool = False,
|
|
47
|
+
custom_server_certificate_path: str = None,
|
|
48
|
+
custom_private_key_path: str = None,
|
|
49
|
+
get_process_name: bool = False,
|
|
50
|
+
ssh_user: str = None,
|
|
51
|
+
ssh_pass: str = None,
|
|
52
|
+
ssh_script_to_execute: Union[
|
|
53
|
+
Literal[
|
|
54
|
+
'process_from_port',
|
|
55
|
+
'process_from_ipv4'
|
|
56
|
+
],
|
|
57
|
+
None
|
|
58
|
+
] = None,
|
|
17
59
|
logger=None,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
domains_list: list = None
|
|
60
|
+
statistics_logs_directory: str = None,
|
|
61
|
+
request_domain_queue: queues.NonBlockQueue = None
|
|
21
62
|
):
|
|
63
|
+
"""
|
|
64
|
+
Socket Wrapper class that will be used to create sockets, listen on them, accept connections and send them to
|
|
65
|
+
new threads.
|
|
66
|
+
|
|
67
|
+
:param listening_interface: string, interface that will be listened on.
|
|
68
|
+
Example: '0.0.0.0'. For all interfaces.
|
|
69
|
+
:param listening_port_list: list, of ports that will be listened on.
|
|
70
|
+
:param ca_certificate_name: CA certificate name.
|
|
71
|
+
:param ca_certificate_filepath: CA certificate file path.
|
|
72
|
+
:param default_server_certificate_usage: boolean, if True, default server certificate will be used
|
|
73
|
+
for each incoming socket.
|
|
74
|
+
:param sni_custom_callback_function: callable, custom callback function that will be executed when
|
|
75
|
+
there is a SNI present in the request.
|
|
76
|
+
|
|
77
|
+
Example: custom callback function to set the 'server_hostname' for the socket with the domain name from SNI:
|
|
78
|
+
def sni_handle(
|
|
79
|
+
sni_ssl_socket: ssl.SSLSocket,
|
|
80
|
+
sni_destination_name: str,
|
|
81
|
+
sni_ssl_context: ssl.SSLContext):
|
|
82
|
+
# Set 'server_hostname' for the socket.
|
|
83
|
+
sni_ssl_socket.server_hostname = sni_destination_name
|
|
84
|
+
|
|
85
|
+
return sni_handle
|
|
86
|
+
|
|
87
|
+
The function should accept 3 arguments:
|
|
88
|
+
sni_ssl_socket: ssl.SSLSocket, SSL socket object.
|
|
89
|
+
sni_destination_name: string, domain name from SNI.
|
|
90
|
+
sni_ssl_context: ssl.SSLContext, SSL context object.
|
|
91
|
+
|
|
92
|
+
These parameters are default for any SNI handler function, so you can use them in your custom function.
|
|
93
|
+
|
|
94
|
+
:param sni_use_default_callback_function: boolean, if True, default callback function will be used.
|
|
95
|
+
The function will set the 'server_hostname' for the socket with the domain name from SNI.
|
|
96
|
+
The example in 'sni_custom_callback_function' parameter is the function that will be used.
|
|
97
|
+
:param sni_use_default_callback_function_extended: boolean, if True, default callback function will be used
|
|
98
|
+
with extended functionality. This feature will handle all the features and parameters that are set in
|
|
99
|
+
the SocketWrapper object that are related to SNI. THis includes certificate management for each domain,
|
|
100
|
+
adding new domains to the default certificate, creating new certificates for each domain, etc.
|
|
101
|
+
This feature also utilizes the 'request_domain_queue' parameter to get the domain name that was requested
|
|
102
|
+
from the DNS server (atomicshop.wrappers.socketw.dns_server).
|
|
103
|
+
:param sni_add_new_domains_to_default_server_certificate: boolean, if True, new domains that hit the tcp
|
|
104
|
+
server will be added to default server certificate.
|
|
105
|
+
:param sni_create_server_certificate_for_each_domain: boolean, if True, server certificate will be
|
|
106
|
+
created and used for each domain that hit the tcp server.
|
|
107
|
+
:param sni_get_server_certificate_from_server_socket: boolean, if True, server certificate will be
|
|
108
|
+
downloaded from the server socket.
|
|
109
|
+
:param sni_server_certificate_from_server_socket_download_directory: string, path to directory where
|
|
110
|
+
server certificate will be downloaded from the server socket.
|
|
111
|
+
:param default_server_certificate_name: default server certificate name.
|
|
112
|
+
:param default_certificate_domain_list: list of string, domains to create the default certificate with.
|
|
113
|
+
:param default_server_certificate_directory: string, path to directory where default certificate file
|
|
114
|
+
will be stored.
|
|
115
|
+
:param sni_server_certificates_cache_directory: string, path to directory where all server certificates for
|
|
116
|
+
each domain will be created.
|
|
117
|
+
:param skip_extension_id_list: list of string, list of extension IDs that will be skipped when processing
|
|
118
|
+
the certificate from the server socket.
|
|
119
|
+
Example: ['1.3.6.1.5.5.7.3.2', '2.5.29.31', '1.3.6.1.5.5.7.1.1']
|
|
120
|
+
:param custom_server_certificate_usage: boolean, if True, custom server certificate will be used.
|
|
121
|
+
:param custom_server_certificate_path: string, path to custom server certificate.
|
|
122
|
+
:param custom_private_key_path: string, path to custom private key.
|
|
123
|
+
server certificates from the server socket.
|
|
124
|
+
:param get_process_name: boolean, if the process name and command line should be gathered from the socket.
|
|
125
|
+
If the socket came from remote host we will try ti get the process name from the remote host by SSH.
|
|
126
|
+
By default, we don't get the process name, because we're using psutil to get the process name and command
|
|
127
|
+
line, but if the process is protected by the system, then command line will be empty.
|
|
128
|
+
It's up to user to decide if to run the script with root privileges or not, this is only relevant if
|
|
129
|
+
the script is running on the same host.
|
|
130
|
+
:param ssh_user: string, SSH username that will be used to connect to remote host.
|
|
131
|
+
:param ssh_pass: string, SSH password that will be used to connect to remote host.
|
|
132
|
+
:param ssh_script_to_execute: string, script that will be executed to get the process name on ssh remote host.
|
|
133
|
+
:param logger: logging.Logger object, logger object that will be used to log messages.
|
|
134
|
+
:param statistics_logs_directory: string, path to directory where daily statistics.csv files will be stored.
|
|
135
|
+
After you initialize the SocketWrapper object, you can get the statistics_writer object from it and use it
|
|
136
|
+
to write statistics to the file in a worker thread.
|
|
137
|
+
|
|
138
|
+
socket_wrapper_instance = SocketWrapper(...)
|
|
139
|
+
statistics_writer = socket_wrapper_instance.statistics_writer
|
|
140
|
+
|
|
141
|
+
statistics_writer: statistics_csv.StatisticsCSVWriter object, there is a logger object that
|
|
142
|
+
will be used to write the statistics file.
|
|
143
|
+
:param request_domain_queue: queues.NonBlockQueue object, non-blocking queue that will be used to get
|
|
144
|
+
the domain name that was requested from the DNS server (atomicshop.wrappers.socketw.dns_server).
|
|
145
|
+
This is used to get the domain name that got to the DNS server and set it to the socket in case SNI
|
|
146
|
+
was empty (in the SNIHandler class to set the 'server_hostname' for the socket).
|
|
147
|
+
"""
|
|
22
148
|
|
|
149
|
+
self.listening_interface: str = listening_interface
|
|
150
|
+
self.listening_port_list: list[int] = listening_port_list
|
|
151
|
+
self.ca_certificate_name: str = ca_certificate_name
|
|
152
|
+
self.ca_certificate_filepath: str = ca_certificate_filepath
|
|
153
|
+
self.default_server_certificate_usage: bool = default_server_certificate_usage
|
|
154
|
+
self.default_server_certificate_name: str = default_server_certificate_name
|
|
155
|
+
self.default_certificate_domain_list: list = default_certificate_domain_list
|
|
156
|
+
self.default_server_certificate_directory: str = default_server_certificate_directory
|
|
157
|
+
self.sni_custom_callback_function: callable = sni_custom_callback_function
|
|
158
|
+
self.sni_use_default_callback_function: bool = sni_use_default_callback_function
|
|
159
|
+
self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
|
|
160
|
+
self.sni_add_new_domains_to_default_server_certificate: bool = sni_add_new_domains_to_default_server_certificate
|
|
161
|
+
self.sni_create_server_certificate_for_each_domain: bool = sni_create_server_certificate_for_each_domain
|
|
162
|
+
self.sni_server_certificates_cache_directory: str = sni_server_certificates_cache_directory
|
|
163
|
+
self.sni_get_server_certificate_from_server_socket: bool = sni_get_server_certificate_from_server_socket
|
|
164
|
+
self.sni_server_certificate_from_server_socket_download_directory: str = \
|
|
165
|
+
sni_server_certificate_from_server_socket_download_directory
|
|
166
|
+
self.skip_extension_id_list: list = skip_extension_id_list
|
|
167
|
+
self.custom_server_certificate_usage: bool = custom_server_certificate_usage
|
|
168
|
+
self.custom_server_certificate_path: str = custom_server_certificate_path
|
|
169
|
+
self.custom_private_key_path: str = custom_private_key_path
|
|
170
|
+
self.get_process_name: bool = get_process_name
|
|
171
|
+
self.ssh_user: str = ssh_user
|
|
172
|
+
self.ssh_pass: str = ssh_pass
|
|
173
|
+
self.ssh_script_to_execute = ssh_script_to_execute
|
|
23
174
|
self.logger = logger
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
|
|
27
|
-
# If 'domains_list' wasn't passed, but 'config' did.
|
|
28
|
-
if not domains_list and config:
|
|
29
|
-
self.domains_list: list = config['certificates']['domains_all_times']
|
|
30
|
-
else:
|
|
31
|
-
self.domains_list: list = domains_list
|
|
175
|
+
self.statistics_logs_directory: str = statistics_logs_directory
|
|
176
|
+
self.forwarding_dns_service_ipv4_list___only_for_localhost = (
|
|
177
|
+
forwarding_dns_service_ipv4_list___only_for_localhost)
|
|
32
178
|
|
|
33
179
|
self.socket_object = None
|
|
34
180
|
|
|
@@ -49,9 +195,80 @@ class SocketWrapper:
|
|
|
49
195
|
|
|
50
196
|
# Defining 'ssh_script_processor' variable, which will be used to process SSH scripts.
|
|
51
197
|
self.ssh_script_processor = None
|
|
52
|
-
if self.
|
|
198
|
+
if self.get_process_name:
|
|
199
|
+
# noinspection PyTypeChecker
|
|
53
200
|
self.ssh_script_processor = \
|
|
54
|
-
ScriptAsStringProcessor().read_script_to_string(self.
|
|
201
|
+
ScriptAsStringProcessor().read_script_to_string(self.ssh_script_to_execute)
|
|
202
|
+
|
|
203
|
+
self.statistics_writer = statistics_csv.StatisticsCSVWriter(
|
|
204
|
+
statistics_directory_path=self.statistics_logs_directory)
|
|
205
|
+
|
|
206
|
+
self.test_config()
|
|
207
|
+
|
|
208
|
+
def test_config(self):
|
|
209
|
+
if self.sni_custom_callback_function and (
|
|
210
|
+
self.sni_use_default_callback_function or self.sni_use_default_callback_function_extended):
|
|
211
|
+
message = "You can't use both custom and default SNI function at the same time."
|
|
212
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
213
|
+
|
|
214
|
+
if self.sni_use_default_callback_function_extended and not self.sni_use_default_callback_function:
|
|
215
|
+
message = "You can't use extended SNI function without default SNI function."
|
|
216
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
217
|
+
|
|
218
|
+
if self.sni_use_default_callback_function and self.sni_custom_callback_function:
|
|
219
|
+
message = \
|
|
220
|
+
"You can't set both [sni_use_default_callback_function = True] and [sni_custom_callback_function]."
|
|
221
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
booleans.check_3_booleans_when_only_1_can_be_true(
|
|
225
|
+
(self.default_server_certificate_usage, 'default_server_certificate_usage'),
|
|
226
|
+
(self.sni_create_server_certificate_for_each_domain,
|
|
227
|
+
'sni_create_server_certificate_for_each_domain'),
|
|
228
|
+
(self.custom_server_certificate_usage, 'custom_server_certificate_usage'))
|
|
229
|
+
except ValueError as e:
|
|
230
|
+
raise SocketWrapperConfigurationValuesError(str(e))
|
|
231
|
+
|
|
232
|
+
if not self.default_server_certificate_usage and \
|
|
233
|
+
self.sni_add_new_domains_to_default_server_certificate:
|
|
234
|
+
message = "No point setting [sni_add_new_domains_to_default_server_certificate = True]\n" \
|
|
235
|
+
"If you're not going to use default certificates [default_server_certificate_usage = False]"
|
|
236
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
237
|
+
|
|
238
|
+
if self.sni_get_server_certificate_from_server_socket and \
|
|
239
|
+
not self.sni_create_server_certificate_for_each_domain:
|
|
240
|
+
message = "You set [sni_get_server_certificate_from_server_socket = True],\n" \
|
|
241
|
+
"But you didn't set [sni_create_server_certificate_for_each_domain = True]."
|
|
242
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
243
|
+
|
|
244
|
+
if self.custom_server_certificate_usage and \
|
|
245
|
+
not self.custom_server_certificate_path:
|
|
246
|
+
message = "You set [custom_server_certificate_usage = True],\n" \
|
|
247
|
+
"But you didn't set [custom_server_certificate_path]."
|
|
248
|
+
raise SocketWrapperConfigurationValuesError(message)
|
|
249
|
+
|
|
250
|
+
# If 'custom_certificate_usage' was set to 'True'.
|
|
251
|
+
if self.custom_server_certificate_usage:
|
|
252
|
+
# Check file existence.
|
|
253
|
+
if not filesystem.is_file_exists(file_path=self.custom_server_certificate_path):
|
|
254
|
+
message = f"File not found: {self.custom_server_certificate_path}"
|
|
255
|
+
print_api(message, color='red')
|
|
256
|
+
return 1
|
|
257
|
+
|
|
258
|
+
# And if 'custom_private_key_path' field was populated in [advanced] section, we'll check its existence.
|
|
259
|
+
if self.custom_private_key_path:
|
|
260
|
+
# Check private key file existence.
|
|
261
|
+
if not filesystem.is_file_exists(file_path=self.custom_private_key_path):
|
|
262
|
+
message = f"File not found: {self.custom_private_key_path}"
|
|
263
|
+
print_api(message, color='red')
|
|
264
|
+
return 1
|
|
265
|
+
|
|
266
|
+
port_in_use = networks.get_processes_using_port_list(self.listening_port_list)
|
|
267
|
+
if port_in_use:
|
|
268
|
+
error_messages: list = list()
|
|
269
|
+
for port, process_info in port_in_use.items():
|
|
270
|
+
error_messages.append(f"Port [{port}] is already in use by process: {process_info}")
|
|
271
|
+
raise SocketWrapperPortInUseError("\n".join(error_messages))
|
|
55
272
|
|
|
56
273
|
# Creating listening sockets.
|
|
57
274
|
def create_socket_ipv4_tcp(self, ip_address: str, port: int):
|
|
@@ -61,9 +278,6 @@ class SocketWrapper:
|
|
|
61
278
|
creator.bind_socket_with_ip_port(self.socket_object, ip_address, port, logger=self.logger)
|
|
62
279
|
creator.set_listen_on_socket(self.socket_object, logger=self.logger)
|
|
63
280
|
|
|
64
|
-
# self.socket_object, accept_error_message = creator.wrap_socket_with_ssl_context_server_sni_extended(
|
|
65
|
-
# self.socket_object, config=self.config, print_kwargs={'logger': self.logger})
|
|
66
|
-
|
|
67
281
|
return self.socket_object
|
|
68
282
|
|
|
69
283
|
def create_tcp_listening_socket_list(self, overwrite_list: bool = False):
|
|
@@ -73,41 +287,41 @@ class SocketWrapper:
|
|
|
73
287
|
self.listening_sockets = list()
|
|
74
288
|
|
|
75
289
|
# Creating a socket for each port in the list set in configuration file
|
|
76
|
-
for port in self.
|
|
290
|
+
for port in self.listening_port_list:
|
|
77
291
|
socket_by_port = self.create_socket_ipv4_tcp(
|
|
78
|
-
self.
|
|
292
|
+
self.listening_interface, port)
|
|
79
293
|
|
|
80
294
|
self.listening_sockets.append(socket_by_port)
|
|
81
295
|
|
|
82
|
-
def send_accepted_socket_to_thread(self, thread_function_name, reference_args=()):
|
|
83
|
-
# Creating thread for each socket
|
|
84
|
-
thread_current = threading.Thread(target=thread_function_name, args=(*reference_args,))
|
|
85
|
-
thread_current.daemon = True
|
|
86
|
-
thread_current.start()
|
|
87
|
-
# Append to list of threads, so they can be "joined" later
|
|
88
|
-
self.threads_list.append(thread_current)
|
|
89
|
-
|
|
90
|
-
# 'reference_args[0]' is the client socket.
|
|
91
|
-
client_address = base.get_source_address_from_socket(reference_args[0])
|
|
92
|
-
|
|
93
|
-
self.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
|
|
94
|
-
|
|
95
296
|
def loop_for_incoming_sockets(
|
|
96
|
-
self,
|
|
97
|
-
|
|
297
|
+
self,
|
|
298
|
+
reference_function_name,
|
|
299
|
+
reference_function_args=(),
|
|
300
|
+
listening_socket_list: list = None,
|
|
301
|
+
pass_function_reference_to_thread: bool = True
|
|
302
|
+
):
|
|
98
303
|
"""
|
|
99
304
|
Loop to wait for new connections, accept them and send to new threads.
|
|
100
305
|
The boolean variable was declared True in the beginning of the script and will be set to False if the process
|
|
101
306
|
will be killed or closed.
|
|
102
307
|
|
|
103
|
-
:param
|
|
104
|
-
socket received by 'accept()' and connection been made.
|
|
308
|
+
:param reference_function_name: callable, function reference that you want to execute when client
|
|
309
|
+
socket received by 'accept()' and connection has been made.
|
|
310
|
+
:param reference_function_args: tuple, that will be passed to 'function_reference' when it will be called.
|
|
311
|
+
Your function should be able to accept these arguments before the 'reference_function_args' tuple:
|
|
312
|
+
(client_socket, process_name, is_tls, domain_from_dns_server).
|
|
313
|
+
Meaning that 'reference_function_args' will be added to the end of the arguments tuple like so:
|
|
314
|
+
(client_socket, process_name, is_tls, tls_type, tls_version, domain_from_dns_server,
|
|
315
|
+
*reference_function_args).
|
|
316
|
+
|
|
317
|
+
client_socket: socket, client socket that was accepted.
|
|
318
|
+
process_name: string, process name that was gathered from the socket.
|
|
319
|
+
is_tls: boolean, if the socket is SSL/TLS.
|
|
320
|
+
domain_from_dns_server: string, domain that was requested from DNS server.
|
|
105
321
|
:param listening_socket_list: list, of sockets that you want to listen on.
|
|
106
322
|
:param pass_function_reference_to_thread: boolean, that sets if 'function_reference' will be
|
|
107
323
|
executed as is, or passed to thread. 'function_reference' can include passing to a thread,
|
|
108
324
|
but you don't have to use it, since SocketWrapper can do it for you.
|
|
109
|
-
:param reference_args: tuple, that will be passed to 'function_reference' when it will be called.
|
|
110
|
-
:param kwargs:
|
|
111
325
|
:return:
|
|
112
326
|
"""
|
|
113
327
|
|
|
@@ -116,10 +330,8 @@ class SocketWrapper:
|
|
|
116
330
|
# Then assign 'self.listening_sockets'.
|
|
117
331
|
listening_socket_list = self.listening_sockets
|
|
118
332
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
socket_infinite_loop_run: bool = True
|
|
122
|
-
while socket_infinite_loop_run:
|
|
333
|
+
while True:
|
|
334
|
+
# noinspection PyBroadException
|
|
123
335
|
try:
|
|
124
336
|
# Using "select.select" which is currently the only API function that works on all
|
|
125
337
|
# operating system types: Windows / Linux / BSD.
|
|
@@ -138,17 +350,20 @@ class SocketWrapper:
|
|
|
138
350
|
# Wait from any connection on "accept()".
|
|
139
351
|
# 'client_socket' is socket or ssl socket, 'client_address' is a tuple (ip_address, port).
|
|
140
352
|
client_socket, client_address, accept_error_message = accepter.accept_connection_with_error(
|
|
141
|
-
listening_socket_object,
|
|
353
|
+
listening_socket_object, domain_from_dns_server=domain_from_dns_server, print_kwargs={'logger': self.logger})
|
|
142
354
|
|
|
143
355
|
# This is the earliest stage to ask for process name.
|
|
144
356
|
# SSH Remote / LOCALHOST script execution to identify process section.
|
|
145
|
-
# If '
|
|
357
|
+
# If 'get_process_name' was set to True, then this will be executed.
|
|
146
358
|
process_name = None
|
|
147
|
-
if self.
|
|
359
|
+
if self.get_process_name:
|
|
148
360
|
# Get the process name from the socket.
|
|
149
|
-
|
|
150
|
-
client_socket=client_socket,
|
|
151
|
-
|
|
361
|
+
get_command_instance = get_process.GetCommandLine(
|
|
362
|
+
client_socket=client_socket,
|
|
363
|
+
ssh_script_processor=self.ssh_script_processor,
|
|
364
|
+
ssh_user=self.ssh_user,
|
|
365
|
+
ssh_pass=self.ssh_pass)
|
|
366
|
+
process_name = get_command_instance.get_process_name(print_kwargs={'logger': self.logger})
|
|
152
367
|
|
|
153
368
|
# If 'accept()' function worked well, SSL worked well, then 'client_socket' won't be empty.
|
|
154
369
|
if client_socket:
|
|
@@ -157,58 +372,99 @@ class SocketWrapper:
|
|
|
157
372
|
tls_properties = ssl_base.is_tls(client_socket)
|
|
158
373
|
if tls_properties:
|
|
159
374
|
is_tls = True
|
|
375
|
+
tls_type, tls_version = tls_properties
|
|
376
|
+
else:
|
|
377
|
+
tls_type, tls_version = None, None
|
|
160
378
|
|
|
161
379
|
# If 'is_tls' is True.
|
|
162
380
|
ssl_client_socket = None
|
|
163
381
|
if is_tls:
|
|
382
|
+
sni_handler = sni.SNISetup(
|
|
383
|
+
default_server_certificate_usage=self.default_server_certificate_usage,
|
|
384
|
+
default_server_certificate_name=self.default_server_certificate_name,
|
|
385
|
+
default_certificate_domain_list=self.default_certificate_domain_list,
|
|
386
|
+
default_server_certificate_directory=self.default_server_certificate_directory,
|
|
387
|
+
sni_custom_callback_function=self.sni_custom_callback_function,
|
|
388
|
+
sni_use_default_callback_function=self.sni_use_default_callback_function,
|
|
389
|
+
sni_use_default_callback_function_extended=self.sni_use_default_callback_function_extended,
|
|
390
|
+
sni_add_new_domains_to_default_server_certificate=(
|
|
391
|
+
self.sni_add_new_domains_to_default_server_certificate),
|
|
392
|
+
sni_server_certificates_cache_directory=self.sni_server_certificates_cache_directory,
|
|
393
|
+
sni_create_server_certificate_for_each_domain=(
|
|
394
|
+
self.sni_create_server_certificate_for_each_domain),
|
|
395
|
+
sni_get_server_certificate_from_server_socket=(
|
|
396
|
+
self.sni_get_server_certificate_from_server_socket),
|
|
397
|
+
sni_server_certificate_from_server_socket_download_directory=(
|
|
398
|
+
self.sni_server_certificate_from_server_socket_download_directory),
|
|
399
|
+
skip_extension_id_list=self.skip_extension_id_list,
|
|
400
|
+
ca_certificate_name=self.ca_certificate_name,
|
|
401
|
+
ca_certificate_filepath=self.ca_certificate_filepath,
|
|
402
|
+
custom_server_certificate_usage=self.custom_server_certificate_usage,
|
|
403
|
+
custom_server_certificate_path=self.custom_server_certificate_path,
|
|
404
|
+
custom_private_key_path=self.custom_private_key_path,
|
|
405
|
+
domain_from_dns_server=domain_from_dns_server,
|
|
406
|
+
forwarding_dns_service_ipv4_list___only_for_localhost=(
|
|
407
|
+
self.forwarding_dns_service_ipv4_list___only_for_localhost),
|
|
408
|
+
tls=is_tls
|
|
409
|
+
)
|
|
410
|
+
|
|
164
411
|
ssl_client_socket, accept_error_message = \
|
|
165
|
-
|
|
166
|
-
client_socket,
|
|
167
|
-
print_kwargs={'logger': self.logger}
|
|
412
|
+
sni_handler.wrap_socket_with_ssl_context_server_sni_extended(
|
|
413
|
+
client_socket,
|
|
414
|
+
print_kwargs={'logger': self.logger}
|
|
415
|
+
)
|
|
168
416
|
|
|
169
417
|
if accept_error_message:
|
|
170
418
|
# Write statistics after wrap is there was an error.
|
|
171
|
-
|
|
172
|
-
error_message=accept_error_message,
|
|
173
|
-
|
|
174
|
-
|
|
419
|
+
self.statistics_writer.write_accept_error(
|
|
420
|
+
error_message=accept_error_message,
|
|
421
|
+
host=domain_from_dns_server,
|
|
422
|
+
process_name=process_name)
|
|
175
423
|
|
|
176
424
|
continue
|
|
177
425
|
|
|
178
|
-
# ready_to_read, _, _ = select.select([client_socket], [], [])
|
|
179
|
-
# if ready_to_read:
|
|
180
|
-
# try:
|
|
181
|
-
# # self.socket_object.do_handshake()
|
|
182
|
-
# self.socket_object.accept()
|
|
183
|
-
# except Exception:
|
|
184
|
-
# raise
|
|
185
|
-
|
|
186
426
|
# Create new arguments tuple that will be passed, since client socket and process_name
|
|
187
427
|
# are gathered from SocketWrapper.
|
|
188
428
|
if ssl_client_socket:
|
|
189
429
|
# In order to use the same object, it needs to get nullified first, since the old instance
|
|
190
430
|
# will not get overwritten. Though it still will show in the memory as SSLSocket, it will not
|
|
191
431
|
# be handled as such, but as regular raw socket.
|
|
432
|
+
# noinspection PyUnusedLocal
|
|
192
433
|
client_socket = None
|
|
193
434
|
client_socket = ssl_client_socket
|
|
194
435
|
thread_args = \
|
|
195
|
-
(client_socket, process_name, is_tls, domain_from_dns_server) +
|
|
436
|
+
((client_socket, process_name, is_tls, tls_type, tls_version, domain_from_dns_server) +
|
|
437
|
+
reference_function_args)
|
|
196
438
|
# If 'pass_function_reference_to_thread' was set to 'False', execute the callable passed function
|
|
197
439
|
# as is.
|
|
198
440
|
if not pass_function_reference_to_thread:
|
|
199
|
-
|
|
441
|
+
reference_function_name(thread_args)
|
|
200
442
|
# If 'pass_function_reference_to_thread' was set to 'True', execute the callable function reference
|
|
201
443
|
# in a new thread.
|
|
202
444
|
else:
|
|
203
|
-
self.
|
|
445
|
+
self._send_accepted_socket_to_thread(reference_function_name, thread_args)
|
|
204
446
|
# Else, if no client_socket was opened during, accept, then print the error.
|
|
205
447
|
else:
|
|
206
448
|
# Write statistics after accept.
|
|
207
|
-
|
|
208
|
-
error_message=accept_error_message,
|
|
209
|
-
|
|
449
|
+
self.statistics_writer.write_accept_error(
|
|
450
|
+
error_message=accept_error_message,
|
|
451
|
+
host=domain_from_dns_server,
|
|
452
|
+
process_name=process_name)
|
|
210
453
|
except Exception:
|
|
211
454
|
print_api("Undocumented exception in while loop of listening sockets.", error_type=True,
|
|
212
|
-
logger_method="error", traceback_string=True,
|
|
455
|
+
logger_method="error", traceback_string=True, logger=self.logger)
|
|
213
456
|
pass
|
|
214
457
|
continue
|
|
458
|
+
|
|
459
|
+
def _send_accepted_socket_to_thread(self, thread_function_name, reference_args=()):
|
|
460
|
+
# Creating thread for each socket
|
|
461
|
+
thread_current = threading.Thread(target=thread_function_name, args=(*reference_args,))
|
|
462
|
+
thread_current.daemon = True
|
|
463
|
+
thread_current.start()
|
|
464
|
+
# Append to list of threads, so they can be "joined" later
|
|
465
|
+
self.threads_list.append(thread_current)
|
|
466
|
+
|
|
467
|
+
# 'reference_args[0]' is the client socket.
|
|
468
|
+
client_address = base.get_source_address_from_socket(reference_args[0])
|
|
469
|
+
|
|
470
|
+
self.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
|
|
@@ -1,23 +1,101 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
3
|
from ...print_api import print_api
|
|
4
|
+
from ...file_io import csvs
|
|
5
|
+
from ..loggingw import loggingw
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
LOGGER_NAME: str = 'statistics'
|
|
9
|
+
STATISTICS_HEADER: str = \
|
|
10
|
+
('request_time_sent,tls,host,path,command,status_code,request_size_bytes,response_size_bytes,file_path,process_cmd,'
|
|
11
|
+
'error')
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
|
|
14
|
+
class StatisticsCSVWriter:
|
|
15
|
+
"""
|
|
16
|
+
Class to write statistics to CSV file.
|
|
17
|
+
This can be initiated at the main, and then passed to the thread worker function.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
statistics_directory_path: str
|
|
22
|
+
):
|
|
23
|
+
self.statistics_directory_path = statistics_directory_path
|
|
24
|
+
|
|
25
|
+
self.csv_logger = loggingw.create_logger(
|
|
26
|
+
logger_name=LOGGER_NAME,
|
|
27
|
+
directory_path=statistics_directory_path,
|
|
28
|
+
add_timedfile=True,
|
|
29
|
+
formatter_filehandler='MESSAGE',
|
|
30
|
+
file_type='csv',
|
|
31
|
+
header=STATISTICS_HEADER
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def write_row(
|
|
35
|
+
self,
|
|
36
|
+
host: str,
|
|
37
|
+
tls_type: str,
|
|
38
|
+
tls_version: str,
|
|
39
|
+
path: str,
|
|
40
|
+
status_code: str,
|
|
41
|
+
command: str,
|
|
42
|
+
request_size_bytes: str,
|
|
43
|
+
response_size_bytes: str,
|
|
44
|
+
recorded_file_path: str = None,
|
|
45
|
+
process_cmd: str = None,
|
|
46
|
+
error: str = None,
|
|
47
|
+
request_time_sent=None,
|
|
48
|
+
):
|
|
49
|
+
if not request_time_sent:
|
|
50
|
+
request_time_sent = datetime.datetime.now()
|
|
51
|
+
|
|
52
|
+
if not tls_type and not tls_version:
|
|
53
|
+
tls_info = ''
|
|
54
|
+
else:
|
|
55
|
+
tls_info = f'{tls_type}|{tls_version}'
|
|
56
|
+
|
|
57
|
+
escaped_line_string: str = csvs.escape_csv_line_to_string([
|
|
58
|
+
request_time_sent,
|
|
59
|
+
tls_info,
|
|
60
|
+
host,
|
|
61
|
+
path,
|
|
62
|
+
command,
|
|
63
|
+
status_code,
|
|
64
|
+
request_size_bytes,
|
|
65
|
+
response_size_bytes,
|
|
66
|
+
recorded_file_path,
|
|
67
|
+
process_cmd,
|
|
68
|
+
error
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
self.csv_logger.info(escaped_line_string)
|
|
72
|
+
|
|
73
|
+
def write_accept_error(
|
|
74
|
+
self,
|
|
75
|
+
error_message: str,
|
|
76
|
+
host: str,
|
|
77
|
+
process_name: str
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Write the error message to the statistics CSV file.
|
|
81
|
+
This is used for easier execution, since most of the parameters will be empty on accept.
|
|
82
|
+
|
|
83
|
+
:param error_message: string, error message.
|
|
84
|
+
:param host: string, host, the domain or IP address.
|
|
85
|
+
:param process_name: process name, the command line of the process.
|
|
86
|
+
:return:
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
self.write_row(
|
|
90
|
+
host=host,
|
|
91
|
+
tls_type='',
|
|
92
|
+
tls_version='',
|
|
93
|
+
path='',
|
|
94
|
+
status_code='',
|
|
95
|
+
command='',
|
|
96
|
+
request_size_bytes='',
|
|
97
|
+
response_size_bytes='',
|
|
98
|
+
recorded_file_path='',
|
|
99
|
+
process_cmd=process_name,
|
|
100
|
+
error=error_message
|
|
17
101
|
)
|
|
18
|
-
except UnboundLocalError:
|
|
19
|
-
pass
|
|
20
|
-
except Exception:
|
|
21
|
-
message = "Undocumented exception after accept on building statistics."
|
|
22
|
-
print_api(message, error_type=True, logger_method='error', traceback_string=True, oneline=True, **print_kwargs)
|
|
23
|
-
pass
|