atomicshop 3.3.29__py3-none-any.whl → 3.4.1__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/mitm/connection_thread_worker.py +289 -142
- atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
- atomicshop/mitm/mitm_main.py +2 -0
- atomicshop/wrappers/githubw.py +170 -59
- atomicshop/wrappers/socketw/certificator.py +13 -3
- atomicshop/wrappers/socketw/creator.py +27 -6
- atomicshop/wrappers/socketw/dns_server.py +5 -5
- atomicshop/wrappers/socketw/sender.py +3 -2
- atomicshop/wrappers/socketw/sni.py +21 -6
- atomicshop/wrappers/socketw/socket_wrapper.py +14 -2
- {atomicshop-3.3.29.dist-info → atomicshop-3.4.1.dist-info}/METADATA +1 -1
- {atomicshop-3.3.29.dist-info → atomicshop-3.4.1.dist-info}/RECORD +16 -16
- {atomicshop-3.3.29.dist-info → atomicshop-3.4.1.dist-info}/WHEEL +0 -0
- {atomicshop-3.3.29.dist-info → atomicshop-3.4.1.dist-info}/licenses/LICENSE.txt +0 -0
- {atomicshop-3.3.29.dist-info → atomicshop-3.4.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -2,6 +2,8 @@ from datetime import datetime
|
|
|
2
2
|
import threading
|
|
3
3
|
import queue
|
|
4
4
|
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
from typing import Literal
|
|
5
7
|
|
|
6
8
|
from ..wrappers.socketw import receiver, sender, socket_client, base
|
|
7
9
|
from .. import websocket_parse, ip_addresses
|
|
@@ -187,13 +189,19 @@ def thread_worker_main(
|
|
|
187
189
|
def create_requester_request(
|
|
188
190
|
client_message: ClientMessage,
|
|
189
191
|
sending_socket: socket.socket
|
|
190
|
-
) ->
|
|
192
|
+
) -> tuple[bytes, bool]:
|
|
193
|
+
request_received_raw: bytes = client_message.request_raw_bytes
|
|
191
194
|
request_custom_raw: bytes = requester.create_request(client_message, sending_socket=sending_socket)
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
if request_custom_raw is None or request_received_raw == request_custom_raw:
|
|
197
|
+
is_requester_worked: bool = False
|
|
198
|
+
else:
|
|
199
|
+
is_requester_worked: bool = True
|
|
200
|
+
|
|
201
|
+
# Output first 100 characters of the request.
|
|
202
|
+
requester.logger.info(f"{request_custom_raw[0: 100]}...")
|
|
195
203
|
|
|
196
|
-
return
|
|
204
|
+
return request_custom_raw, is_requester_worked
|
|
197
205
|
|
|
198
206
|
def create_responder_response(client_message: ClientMessage) -> list[bytes]:
|
|
199
207
|
if client_message.action == 'service_connect':
|
|
@@ -335,6 +343,260 @@ def thread_worker_main(
|
|
|
335
343
|
|
|
336
344
|
return client_message
|
|
337
345
|
|
|
346
|
+
def receive_send_service_connect(
|
|
347
|
+
client_connection_message: ClientMessage,
|
|
348
|
+
sending_socket: ssl.SSLSocket | socket.socket
|
|
349
|
+
) -> Literal['continue', 'return'] | None:
|
|
350
|
+
|
|
351
|
+
nonlocal exception_or_close_in_receiving_thread
|
|
352
|
+
|
|
353
|
+
client_message = client_connection_message
|
|
354
|
+
|
|
355
|
+
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
356
|
+
print_api(f"Got responses from connect responder, count: [{len(bytes_to_send_list)}]",
|
|
357
|
+
logger=network_logger, logger_method='info')
|
|
358
|
+
|
|
359
|
+
# If the client message is the connection message, then we'll skip to the next iteration.
|
|
360
|
+
if not bytes_to_send_list:
|
|
361
|
+
return 'continue'
|
|
362
|
+
|
|
363
|
+
# is_socket_closed: bool = False
|
|
364
|
+
error_on_send: str = str()
|
|
365
|
+
for bytes_to_send_single in bytes_to_send_list:
|
|
366
|
+
client_message.reinitialize_dynamic_vars()
|
|
367
|
+
client_message.timestamp = datetime.now()
|
|
368
|
+
client_message.response_raw_bytes = bytes_to_send_single
|
|
369
|
+
client_message.action = 'service_responder'
|
|
370
|
+
record_and_statistics_write(client_message)
|
|
371
|
+
|
|
372
|
+
# Send the bytes back to the client socket.
|
|
373
|
+
error_on_send: str = sender.Sender(
|
|
374
|
+
ssl_socket=sending_socket, class_message=bytes_to_send_single,
|
|
375
|
+
logger=network_logger).send()
|
|
376
|
+
|
|
377
|
+
if error_on_send:
|
|
378
|
+
client_message.reinitialize_dynamic_vars()
|
|
379
|
+
client_message.errors.append(error_on_send)
|
|
380
|
+
client_message.timestamp = datetime.now()
|
|
381
|
+
client_message.action = 'client_send'
|
|
382
|
+
record_and_statistics_write(client_message)
|
|
383
|
+
|
|
384
|
+
if error_on_send:
|
|
385
|
+
exception_or_close_in_receiving_thread = True
|
|
386
|
+
finish_thread()
|
|
387
|
+
return 'return'
|
|
388
|
+
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
def receive_send_client_offline(
|
|
392
|
+
client_message: ClientMessage,
|
|
393
|
+
receiving_socket: ssl.SSLSocket | socket.socket,
|
|
394
|
+
sending_socket: ssl.SSLSocket | socket.socket
|
|
395
|
+
) -> Literal['return'] | None:
|
|
396
|
+
nonlocal exception_or_close_in_receiving_thread
|
|
397
|
+
nonlocal client_receive_count
|
|
398
|
+
|
|
399
|
+
client_receive_count += 1
|
|
400
|
+
|
|
401
|
+
network_logger.info(f"Initializing Receiver for Client cycle: {str(client_receive_count)}")
|
|
402
|
+
client_message.timestamp = datetime.now()
|
|
403
|
+
|
|
404
|
+
received_raw_data, is_socket_closed, error_message = receiver.Receiver(
|
|
405
|
+
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
406
|
+
|
|
407
|
+
process_client_raw_data(received_raw_data, error_message, client_message)
|
|
408
|
+
client_message.action = 'client_receive'
|
|
409
|
+
|
|
410
|
+
record_and_statistics_write(client_message)
|
|
411
|
+
|
|
412
|
+
# If there was an exception in the service thread, then receiving empty bytes doesn't mean that
|
|
413
|
+
# the socket was closed by the other side, it means that the service thread closed the socket.
|
|
414
|
+
if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
|
|
415
|
+
print_api("Both sockets are closed, breaking the loop", logger=network_logger,
|
|
416
|
+
logger_method='info')
|
|
417
|
+
return 'return'
|
|
418
|
+
|
|
419
|
+
# If the socket was closed on receive, and we're in offline mode, then we'll finish the thread right away.
|
|
420
|
+
# Since nothing more can be done, like responding to service or using requester.
|
|
421
|
+
if is_socket_closed:
|
|
422
|
+
exception_or_close_in_receiving_thread = True
|
|
423
|
+
finish_thread()
|
|
424
|
+
return 'return'
|
|
425
|
+
|
|
426
|
+
# Send to requester.
|
|
427
|
+
# THERE IS ALWAYS WILL BE ONLY ONE REQUEST FROM REQUESTER, SINCE THIS IS WHAT WE GOT FROM THE CLIENT.
|
|
428
|
+
request_custom_raw, is_requester_worked = create_requester_request(client_message, sending_socket=sending_socket)
|
|
429
|
+
# We will not process the raw data if requester didn't change anything.
|
|
430
|
+
if is_requester_worked:
|
|
431
|
+
client_message.reinitialize_dynamic_vars()
|
|
432
|
+
client_message.timestamp = datetime.now()
|
|
433
|
+
client_message.request_raw_bytes = request_custom_raw
|
|
434
|
+
client_message.action = 'client_requester'
|
|
435
|
+
process_client_raw_data(request_custom_raw, error_message, client_message)
|
|
436
|
+
record_and_statistics_write(client_message)
|
|
437
|
+
|
|
438
|
+
print_api("Offline Mode, sending to responder directly.", logger=network_logger,
|
|
439
|
+
logger_method='info')
|
|
440
|
+
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
441
|
+
|
|
442
|
+
error_on_send: str = str()
|
|
443
|
+
for bytes_to_send_single in bytes_to_send_list:
|
|
444
|
+
client_message.reinitialize_dynamic_vars()
|
|
445
|
+
client_message.timestamp = datetime.now()
|
|
446
|
+
client_message.response_raw_bytes = bytes_to_send_single
|
|
447
|
+
client_message.action = 'client_responder_offline'
|
|
448
|
+
process_server_raw_data(bytes_to_send_single, '', client_message)
|
|
449
|
+
record_and_statistics_write(client_message)
|
|
450
|
+
|
|
451
|
+
error_on_send: str = sender.Sender(
|
|
452
|
+
ssl_socket=receiving_socket, class_message=bytes_to_send_single,
|
|
453
|
+
logger=network_logger).send()
|
|
454
|
+
|
|
455
|
+
if error_on_send:
|
|
456
|
+
client_message.reinitialize_dynamic_vars()
|
|
457
|
+
client_message.errors.append(error_on_send)
|
|
458
|
+
client_message.timestamp = datetime.now()
|
|
459
|
+
client_message.action = 'service_send'
|
|
460
|
+
|
|
461
|
+
record_and_statistics_write(client_message)
|
|
462
|
+
|
|
463
|
+
# If the socket was closed on message receive, then we'll break the loop only after send.
|
|
464
|
+
if is_socket_closed or error_on_send:
|
|
465
|
+
exception_or_close_in_receiving_thread = True
|
|
466
|
+
finish_thread()
|
|
467
|
+
return 'return'
|
|
468
|
+
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
def receive_send_client(
|
|
472
|
+
client_message: ClientMessage,
|
|
473
|
+
receiving_socket: ssl.SSLSocket | socket.socket,
|
|
474
|
+
sending_socket: ssl.SSLSocket | socket.socket
|
|
475
|
+
) -> Literal['return'] | None:
|
|
476
|
+
|
|
477
|
+
nonlocal exception_or_close_in_receiving_thread
|
|
478
|
+
nonlocal client_receive_count
|
|
479
|
+
|
|
480
|
+
client_receive_count += 1
|
|
481
|
+
|
|
482
|
+
network_logger.info(f"Initializing Receiver for Client cycle: {str(client_receive_count)}")
|
|
483
|
+
|
|
484
|
+
# Getting message from the client over the socket using specific class.
|
|
485
|
+
client_message.timestamp = datetime.now()
|
|
486
|
+
received_raw_data, is_socket_closed, error_message = receiver.Receiver(
|
|
487
|
+
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
488
|
+
|
|
489
|
+
process_client_raw_data(received_raw_data, error_message, client_message)
|
|
490
|
+
client_message.action = 'client_receive'
|
|
491
|
+
|
|
492
|
+
record_and_statistics_write(client_message)
|
|
493
|
+
|
|
494
|
+
# If there was an exception in the service thread, then receiving empty bytes doesn't mean that
|
|
495
|
+
# the socket was closed by the other side, it means that the service thread closed the socket.
|
|
496
|
+
if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
|
|
497
|
+
print_api("Both sockets are closed, breaking the loop", logger=network_logger,
|
|
498
|
+
logger_method='info')
|
|
499
|
+
return 'return'
|
|
500
|
+
|
|
501
|
+
# Send to requester.
|
|
502
|
+
# THERE IS ALWAYS WILL BE ONLY ONE REQUEST FROM REQUESTER, SINCE THIS IS WHAT WE GOT FROM THE CLIENT.
|
|
503
|
+
request_custom_raw, is_requester_worked = create_requester_request(client_message, sending_socket=sending_socket)
|
|
504
|
+
# We will not process the raw data if requester didn't change anything.
|
|
505
|
+
if is_requester_worked:
|
|
506
|
+
client_message.reinitialize_dynamic_vars()
|
|
507
|
+
client_message.timestamp = datetime.now()
|
|
508
|
+
client_message.request_raw_bytes = request_custom_raw
|
|
509
|
+
client_message.action = 'client_requester'
|
|
510
|
+
process_client_raw_data(request_custom_raw, error_message, client_message)
|
|
511
|
+
record_and_statistics_write(client_message)
|
|
512
|
+
|
|
513
|
+
error_on_send: str = sender.Sender(
|
|
514
|
+
ssl_socket=sending_socket, class_message=client_message.request_raw_bytes,
|
|
515
|
+
logger=network_logger).send()
|
|
516
|
+
|
|
517
|
+
if error_on_send:
|
|
518
|
+
client_message.reinitialize_dynamic_vars()
|
|
519
|
+
client_message.errors.append(error_on_send)
|
|
520
|
+
client_message.timestamp = datetime.now()
|
|
521
|
+
client_message.action = 'service_send'
|
|
522
|
+
record_and_statistics_write(client_message)
|
|
523
|
+
|
|
524
|
+
# If the socket was closed on message receive, then we'll break the loop only after send.
|
|
525
|
+
if is_socket_closed or error_on_send:
|
|
526
|
+
exception_or_close_in_receiving_thread = True
|
|
527
|
+
finish_thread()
|
|
528
|
+
return 'return'
|
|
529
|
+
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
def receive_send_service(
|
|
533
|
+
client_message: ClientMessage,
|
|
534
|
+
receiving_socket: ssl.SSLSocket | socket.socket,
|
|
535
|
+
sending_socket: ssl.SSLSocket | socket.socket
|
|
536
|
+
) -> Literal['return'] | None:
|
|
537
|
+
|
|
538
|
+
nonlocal exception_or_close_in_receiving_thread
|
|
539
|
+
nonlocal server_receive_count
|
|
540
|
+
|
|
541
|
+
server_receive_count += 1
|
|
542
|
+
|
|
543
|
+
network_logger.info(f"Initializing Receiver for Service cycle: {str(server_receive_count)}")
|
|
544
|
+
|
|
545
|
+
# Getting message from the client over the socket using specific class.
|
|
546
|
+
client_message.timestamp = datetime.now()
|
|
547
|
+
received_raw_data, is_socket_closed, error_message = receiver.Receiver(
|
|
548
|
+
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
549
|
+
|
|
550
|
+
process_server_raw_data(received_raw_data, error_message, client_message)
|
|
551
|
+
client_message.action = 'service_receive'
|
|
552
|
+
record_and_statistics_write(client_message)
|
|
553
|
+
|
|
554
|
+
# If there was an exception in the service thread, then receiving empty bytes doesn't mean that
|
|
555
|
+
# the socket was closed by the other side, it means that the service thread closed the socket.
|
|
556
|
+
if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
|
|
557
|
+
print_api("Both sockets are closed, breaking the loop", logger=network_logger,
|
|
558
|
+
logger_method='info')
|
|
559
|
+
return 'return'
|
|
560
|
+
|
|
561
|
+
# Now send it to requester/responder.
|
|
562
|
+
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
563
|
+
print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
|
|
564
|
+
logger=network_logger, logger_method='info')
|
|
565
|
+
|
|
566
|
+
# is_socket_closed: bool = False
|
|
567
|
+
error_on_send: str = str()
|
|
568
|
+
for bytes_to_send_single in bytes_to_send_list:
|
|
569
|
+
client_message.reinitialize_dynamic_vars()
|
|
570
|
+
client_message.timestamp = datetime.now()
|
|
571
|
+
client_message.response_raw_bytes = bytes_to_send_single
|
|
572
|
+
|
|
573
|
+
# This records the requester or responder output, only if it is not the same as the original
|
|
574
|
+
# message.
|
|
575
|
+
if bytes_to_send_single != received_raw_data:
|
|
576
|
+
client_message.action = 'service_responder'
|
|
577
|
+
record_and_statistics_write(client_message)
|
|
578
|
+
|
|
579
|
+
error_on_send: str = sender.Sender(
|
|
580
|
+
ssl_socket=sending_socket, class_message=bytes_to_send_single,
|
|
581
|
+
logger=network_logger).send()
|
|
582
|
+
|
|
583
|
+
if error_on_send:
|
|
584
|
+
client_message.reinitialize_dynamic_vars()
|
|
585
|
+
client_message.errors.append(error_on_send)
|
|
586
|
+
client_message.timestamp = datetime.now()
|
|
587
|
+
client_message.action = 'client_send'
|
|
588
|
+
|
|
589
|
+
record_and_statistics_write(client_message)
|
|
590
|
+
|
|
591
|
+
# If the socket was closed on message receive, then we'll break the loop only after send.
|
|
592
|
+
if is_socket_closed or error_on_send:
|
|
593
|
+
exception_or_close_in_receiving_thread = True
|
|
594
|
+
finish_thread()
|
|
595
|
+
return 'return'
|
|
596
|
+
|
|
597
|
+
return None
|
|
598
|
+
|
|
599
|
+
|
|
338
600
|
def receive_send_start(
|
|
339
601
|
receiving_socket,
|
|
340
602
|
sending_socket = None,
|
|
@@ -360,146 +622,25 @@ def thread_worker_main(
|
|
|
360
622
|
raise ValueError(f"Unknown side of the socket: {receiving_socket}")
|
|
361
623
|
|
|
362
624
|
while True:
|
|
363
|
-
|
|
364
|
-
# pass the socket connect to responder.
|
|
365
|
-
if side == 'Service' and client_connection_message:
|
|
366
|
-
client_message = client_connection_message
|
|
367
|
-
|
|
368
|
-
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
369
|
-
print_api(f"Got responses from connect responder, count: [{len(bytes_to_send_list)}]", logger=network_logger,
|
|
370
|
-
logger_method='info')
|
|
625
|
+
client_message.reinitialize_dynamic_vars()
|
|
371
626
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if side == 'Client':
|
|
377
|
-
client_receive_count += 1
|
|
378
|
-
current_count = client_receive_count
|
|
379
|
-
else:
|
|
380
|
-
server_receive_count += 1
|
|
381
|
-
current_count = server_receive_count
|
|
382
|
-
|
|
383
|
-
# Getting current time of message received, either from client or service.
|
|
384
|
-
client_message.timestamp = datetime.now()
|
|
385
|
-
|
|
386
|
-
# # No need to receive on service socket if we're in offline mode, because there is no service to connect to.
|
|
387
|
-
# if config_static.MainConfig.offline and side == 'Service':
|
|
388
|
-
# print_api("Offline Mode, skipping receiving on service socket.", logger=network_logger,
|
|
389
|
-
# logger_method='info')
|
|
390
|
-
# else:
|
|
391
|
-
|
|
392
|
-
network_logger.info(
|
|
393
|
-
f"Initializing Receiver for {side} cycle: {str(current_count)}")
|
|
394
|
-
|
|
395
|
-
# Getting message from the client over the socket using specific class.
|
|
396
|
-
received_raw_data, is_socket_closed, error_message = receiver.Receiver(
|
|
397
|
-
ssl_socket=receiving_socket, logger=network_logger).receive()
|
|
398
|
-
|
|
399
|
-
# In case of client socket, we'll process the raw data specifically for the client.
|
|
400
|
-
if side == 'Client':
|
|
401
|
-
process_client_raw_data(received_raw_data, error_message, client_message)
|
|
402
|
-
client_message.action = 'client_receive'
|
|
403
|
-
# In case of service socket, we'll process the raw data specifically for the service.
|
|
404
|
-
else:
|
|
405
|
-
process_server_raw_data(received_raw_data, error_message, client_message)
|
|
406
|
-
client_message.action = 'service_receive'
|
|
407
|
-
|
|
408
|
-
# If there was an exception in the service thread, then receiving empty bytes doesn't mean that
|
|
409
|
-
# the socket was closed by the other side, it means that the service thread closed the socket.
|
|
410
|
-
if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
|
|
411
|
-
print_api("Both sockets are closed, breaking the loop", logger=network_logger,
|
|
412
|
-
logger_method='info')
|
|
413
|
-
return
|
|
414
|
-
|
|
415
|
-
# We will record only if there was no closing signal, because if there was, it means that we initiated
|
|
416
|
-
# the close on the opposite socket.
|
|
417
|
-
record_and_statistics_write(client_message)
|
|
418
|
-
|
|
419
|
-
# if is_socket_closed:
|
|
420
|
-
# exception_or_close_in_receiving_thread = True
|
|
421
|
-
# finish_thread()
|
|
422
|
-
# return
|
|
423
|
-
|
|
424
|
-
# Now send it to requester/responder.
|
|
425
|
-
if side == 'Client':
|
|
426
|
-
# Send to requester.
|
|
427
|
-
bytes_to_send_list: list[bytes] = create_requester_request(
|
|
428
|
-
client_message, sending_socket=sending_socket)
|
|
429
|
-
|
|
430
|
-
# If we're in offline mode, then we'll put the request to the responder right away.
|
|
431
|
-
if config_static.MainConfig.offline:
|
|
432
|
-
print_api("Offline Mode, sending to responder directly.", logger=network_logger,
|
|
433
|
-
logger_method='info')
|
|
434
|
-
process_client_raw_data(bytes_to_send_list[0], error_message, client_message)
|
|
435
|
-
bytes_to_send_list = create_responder_response(client_message)
|
|
436
|
-
elif side == 'Service':
|
|
437
|
-
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
438
|
-
print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
|
|
439
|
-
logger=network_logger,
|
|
440
|
-
logger_method='info')
|
|
441
|
-
else:
|
|
442
|
-
raise ValueError(f"Unknown side [{side}] of the socket: {receiving_socket}")
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
# If nothing was passed from the responder, and the client message is the connection message, then we'll skip to the next iteration.
|
|
446
|
-
if not bytes_to_send_list and client_connection_message:
|
|
627
|
+
if side == 'Service' and client_connection_message:
|
|
628
|
+
result: Literal['continue', 'return'] | None = (
|
|
629
|
+
receive_send_service_connect(client_connection_message, sending_socket))
|
|
447
630
|
client_connection_message = None
|
|
448
|
-
continue
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
client_message
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
else:
|
|
459
|
-
client_message.response_raw_bytes = bytes_to_send_single
|
|
460
|
-
|
|
461
|
-
# This records the requester or responder output, only if it is not the same as the original
|
|
462
|
-
# message.
|
|
463
|
-
if bytes_to_send_single != received_raw_data:
|
|
464
|
-
if side == 'Client':
|
|
465
|
-
client_message.action = 'client_requester'
|
|
466
|
-
|
|
467
|
-
if config_static.MainConfig.offline:
|
|
468
|
-
client_message.action = 'client_responder_offline'
|
|
469
|
-
elif side == 'Service':
|
|
470
|
-
client_message.action = 'service_responder'
|
|
471
|
-
record_and_statistics_write(client_message)
|
|
472
|
-
|
|
473
|
-
# If we're in offline mode, it means we're in the client thread, and we'll send the
|
|
474
|
-
# bytes back to the client socket.
|
|
475
|
-
if config_static.MainConfig.offline:
|
|
476
|
-
error_on_send: str = sender.Sender(
|
|
477
|
-
ssl_socket=receiving_socket, class_message=bytes_to_send_single,
|
|
478
|
-
logger=network_logger).send()
|
|
479
|
-
else:
|
|
480
|
-
error_on_send: str = sender.Sender(
|
|
481
|
-
ssl_socket=sending_socket, class_message=bytes_to_send_single,
|
|
482
|
-
logger=network_logger).send()
|
|
483
|
-
|
|
484
|
-
if error_on_send:
|
|
485
|
-
client_message.reinitialize_dynamic_vars()
|
|
486
|
-
client_message.errors.append(error_on_send)
|
|
487
|
-
client_message.timestamp = datetime.now()
|
|
488
|
-
if side == 'Client':
|
|
489
|
-
client_message.action = 'service_send'
|
|
490
|
-
else:
|
|
491
|
-
client_message.action = 'client_send'
|
|
492
|
-
|
|
493
|
-
record_and_statistics_write(client_message)
|
|
494
|
-
|
|
495
|
-
# If the socket was closed on message receive, then we'll break the loop only after send.
|
|
496
|
-
if is_socket_closed or error_on_send:
|
|
497
|
-
exception_or_close_in_receiving_thread = True
|
|
498
|
-
finish_thread()
|
|
499
|
-
return
|
|
631
|
+
if result == 'continue':
|
|
632
|
+
continue
|
|
633
|
+
elif side == 'Client' and config_static.MainConfig.offline:
|
|
634
|
+
result: Literal['return'] | None = receive_send_client_offline(client_message, receiving_socket, sending_socket)
|
|
635
|
+
elif side == 'Client':
|
|
636
|
+
result: Literal['return'] | None = receive_send_client(client_message, receiving_socket, sending_socket)
|
|
637
|
+
elif side == 'Service':
|
|
638
|
+
result: Literal['return'] | None = receive_send_service(client_message, receiving_socket, sending_socket)
|
|
639
|
+
else:
|
|
640
|
+
raise ValueError(f"Unknown side [{side}] of the socket: {receiving_socket}")
|
|
500
641
|
|
|
501
|
-
|
|
502
|
-
|
|
642
|
+
if result == 'return':
|
|
643
|
+
return
|
|
503
644
|
except Exception as exc:
|
|
504
645
|
# If the sockets were already closed, then there is nothing to do here besides log.
|
|
505
646
|
# if (isinstance(exc, OSError) and exc.errno == 10038 and
|
|
@@ -602,7 +743,13 @@ def thread_worker_main(
|
|
|
602
743
|
try:
|
|
603
744
|
engine_name: str = recorder.engine_name
|
|
604
745
|
client_ip, source_port = client_socket.getpeername()
|
|
605
|
-
|
|
746
|
+
|
|
747
|
+
try:
|
|
748
|
+
client_name: str = socket.gethostbyaddr(client_ip)[0]
|
|
749
|
+
# This can happen if the host changed IP address, but it wasn't propagated over DHCP.
|
|
750
|
+
except socket.herror:
|
|
751
|
+
client_name = ""
|
|
752
|
+
|
|
606
753
|
client_name = client_name.lower()
|
|
607
754
|
destination_port: int = client_socket.getsockname()[1]
|
|
608
755
|
destination_port_str: str = str(destination_port)
|
|
@@ -112,5 +112,5 @@ class RequesterParent:
|
|
|
112
112
|
def create_request(self, class_client_message: ClientMessage, **kwargs) -> bytes:
|
|
113
113
|
""" This function should be overridden in the child class. """
|
|
114
114
|
|
|
115
|
-
request_bytes: bytes =
|
|
115
|
+
request_bytes: bytes = None
|
|
116
116
|
return request_bytes
|
atomicshop/mitm/mitm_main.py
CHANGED
|
@@ -502,6 +502,8 @@ def mitm_server(config_file_path: str, script_version: str):
|
|
|
502
502
|
exceptions_logger_queue=EXCEPTIONS_CSV_LOGGER_QUEUE,
|
|
503
503
|
forwarding_dns_service_ipv4_list___only_for_localhost=[config_static.DNSServer.forwarding_dns_service_ipv4],
|
|
504
504
|
skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST,
|
|
505
|
+
enable_sslkeylogfile_env_to_client_ssl_context=config_static.Certificates.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
506
|
+
sslkeylog_file_path=config_static.Certificates.sslkeylog_file_path,
|
|
505
507
|
print_kwargs=dict(stdout=False)
|
|
506
508
|
)
|
|
507
509
|
|
atomicshop/wrappers/githubw.py
CHANGED
|
@@ -163,11 +163,103 @@ class GitHubWrapper:
|
|
|
163
163
|
|
|
164
164
|
self.build_links_from_user_and_repo()
|
|
165
165
|
|
|
166
|
-
def check_github_domain(
|
|
166
|
+
def check_github_domain(
|
|
167
|
+
self,
|
|
168
|
+
domain: str
|
|
169
|
+
):
|
|
167
170
|
if self.domain not in domain:
|
|
168
171
|
print_api(
|
|
169
172
|
f'This is not [{self.domain}] domain.', color="red", error_type=True)
|
|
170
173
|
|
|
174
|
+
def download_file(
|
|
175
|
+
self,
|
|
176
|
+
file_name: str,
|
|
177
|
+
target_dir: str
|
|
178
|
+
) -> str:
|
|
179
|
+
"""
|
|
180
|
+
Download a single repo file to a local directory.
|
|
181
|
+
|
|
182
|
+
:param file_name: string, Full repo-relative path to the file. Example:
|
|
183
|
+
"eng.traineddata"
|
|
184
|
+
"script\\English.script"
|
|
185
|
+
:param target_dir: string, Local directory to save into.
|
|
186
|
+
|
|
187
|
+
:return: The local path to the downloaded file.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
# Normalize to GitHub path format
|
|
191
|
+
file_path = file_name.replace("\\", "/").strip("/")
|
|
192
|
+
|
|
193
|
+
headers = self._get_headers()
|
|
194
|
+
url = f"{self.contents_url}/{file_path}"
|
|
195
|
+
params = {"ref": self.branch}
|
|
196
|
+
|
|
197
|
+
resp = requests.get(url, headers=headers, params=params)
|
|
198
|
+
resp.raise_for_status()
|
|
199
|
+
item = resp.json()
|
|
200
|
+
|
|
201
|
+
# Expect a single file object
|
|
202
|
+
if isinstance(item, list) or item.get("type") != "file":
|
|
203
|
+
raise ValueError(f"'{file_name}' is not a file in branch '{self.branch}'.")
|
|
204
|
+
|
|
205
|
+
download_url = item.get("download_url")
|
|
206
|
+
if not download_url:
|
|
207
|
+
raise ValueError(f"Unable to obtain download URL for '{file_name}'.")
|
|
208
|
+
|
|
209
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
210
|
+
local_name = item.get("name") or os.path.basename(file_path)
|
|
211
|
+
|
|
212
|
+
from .. import web # ensure available in your module structure
|
|
213
|
+
web.download(
|
|
214
|
+
file_url=download_url,
|
|
215
|
+
target_directory=target_dir,
|
|
216
|
+
file_name=local_name,
|
|
217
|
+
headers=headers,
|
|
218
|
+
)
|
|
219
|
+
return os.path.join(target_dir, local_name)
|
|
220
|
+
|
|
221
|
+
def download_directory(
|
|
222
|
+
self,
|
|
223
|
+
folder_name: str,
|
|
224
|
+
target_dir: str
|
|
225
|
+
) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Recursively download a repo directory to a local directory.
|
|
228
|
+
|
|
229
|
+
:param folder_name: string, Repo-relative directory path to download (e.g., "tests/langs").
|
|
230
|
+
:param target_dir: string, Local directory to save the folder tree into.
|
|
231
|
+
"""
|
|
232
|
+
headers = self._get_headers()
|
|
233
|
+
root_path = folder_name.replace("\\", "/").strip("/")
|
|
234
|
+
|
|
235
|
+
def _walk_dir(rel_path: str, local_dir: str) -> None:
|
|
236
|
+
contents_url = f"{self.contents_url}/{rel_path}" if rel_path else self.contents_url
|
|
237
|
+
params = {"ref": self.branch}
|
|
238
|
+
|
|
239
|
+
response = requests.get(contents_url, headers=headers, params=params)
|
|
240
|
+
response.raise_for_status()
|
|
241
|
+
items = response.json()
|
|
242
|
+
|
|
243
|
+
# If a file path was passed accidentally, delegate to download_file
|
|
244
|
+
if isinstance(items, dict) and items.get("type") == "file":
|
|
245
|
+
self.download_file(rel_path, local_dir)
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
if not isinstance(items, list):
|
|
249
|
+
raise ValueError(f"Unexpected response shape when listing '{rel_path or '/'}'.")
|
|
250
|
+
|
|
251
|
+
os.makedirs(local_dir, exist_ok=True)
|
|
252
|
+
|
|
253
|
+
for item in items:
|
|
254
|
+
name = item["name"]
|
|
255
|
+
if item["type"] == "file":
|
|
256
|
+
self.download_file(f"{rel_path}/{name}" if rel_path else name, local_dir)
|
|
257
|
+
elif item["type"] == "dir":
|
|
258
|
+
_walk_dir(f"{rel_path}/{name}" if rel_path else name, os.path.join(local_dir, name))
|
|
259
|
+
# ignore symlinks/submodules if present
|
|
260
|
+
|
|
261
|
+
_walk_dir(root_path, target_dir)
|
|
262
|
+
|
|
171
263
|
def download_and_extract_branch(
|
|
172
264
|
self,
|
|
173
265
|
target_directory: str,
|
|
@@ -195,48 +287,6 @@ class GitHubWrapper:
|
|
|
195
287
|
:return:
|
|
196
288
|
"""
|
|
197
289
|
|
|
198
|
-
def download_file(file_url: str, target_dir: str, file_name: str, current_headers: dict) -> None:
|
|
199
|
-
os.makedirs(target_dir, exist_ok=True)
|
|
200
|
-
|
|
201
|
-
web.download(
|
|
202
|
-
file_url=file_url,
|
|
203
|
-
target_directory=target_dir,
|
|
204
|
-
file_name=file_name,
|
|
205
|
-
headers=current_headers
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
def download_directory(folder_path: str, target_dir: str, current_headers: dict) -> None:
|
|
209
|
-
# Construct the API URL for the current folder.
|
|
210
|
-
contents_url = f"{self.contents_url}/{folder_path}"
|
|
211
|
-
params = {'ref': self.branch}
|
|
212
|
-
|
|
213
|
-
response = requests.get(contents_url, headers=current_headers, params=params)
|
|
214
|
-
response.raise_for_status()
|
|
215
|
-
|
|
216
|
-
# Get the list of items (files and subdirectories) in the folder.
|
|
217
|
-
items = response.json()
|
|
218
|
-
|
|
219
|
-
# Ensure the local target directory exists.
|
|
220
|
-
os.makedirs(target_dir, exist_ok=True)
|
|
221
|
-
|
|
222
|
-
# Process each item.
|
|
223
|
-
for item in items:
|
|
224
|
-
local_item_path = os.path.join(target_dir, item['name'])
|
|
225
|
-
if item['type'] == 'file':
|
|
226
|
-
download_file(
|
|
227
|
-
file_url=item['download_url'],
|
|
228
|
-
target_dir=target_dir,
|
|
229
|
-
file_name=item['name'],
|
|
230
|
-
current_headers=current_headers
|
|
231
|
-
)
|
|
232
|
-
elif item['type'] == 'dir':
|
|
233
|
-
# Recursively download subdirectories.
|
|
234
|
-
download_directory(
|
|
235
|
-
folder_path=f"{folder_path}/{item['name']}",
|
|
236
|
-
target_dir=local_item_path,
|
|
237
|
-
current_headers=current_headers
|
|
238
|
-
)
|
|
239
|
-
|
|
240
290
|
headers: dict = self._get_headers()
|
|
241
291
|
|
|
242
292
|
if not download_each_file:
|
|
@@ -275,7 +325,7 @@ class GitHubWrapper:
|
|
|
275
325
|
else:
|
|
276
326
|
current_target_directory = os.path.join(target_directory, self.path)
|
|
277
327
|
|
|
278
|
-
download_directory(self.path, current_target_directory
|
|
328
|
+
self.download_directory(self.path, current_target_directory)
|
|
279
329
|
|
|
280
330
|
def get_releases_json(
|
|
281
331
|
self,
|
|
@@ -530,8 +580,77 @@ class GitHubWrapper:
|
|
|
530
580
|
commit_message = latest_commit.get("commit", {}).get("message", "")
|
|
531
581
|
return commit_message
|
|
532
582
|
|
|
583
|
+
def list_files(
|
|
584
|
+
self,
|
|
585
|
+
pattern: str = "*",
|
|
586
|
+
recursive: bool = True,
|
|
587
|
+
path: str | None = None,
|
|
588
|
+
) -> list[str]:
|
|
589
|
+
"""
|
|
590
|
+
List files in the repository (or in a specific subfolder).
|
|
533
591
|
|
|
534
|
-
|
|
592
|
+
:param pattern: Glob-style pattern (e.g., "*.ex*", "*test*.py"). Matching is done
|
|
593
|
+
against the file's base name (not the full path).
|
|
594
|
+
:param recursive: If True, include files in all subfolders (returns full repo-relative
|
|
595
|
+
paths). If False, list only the immediate files in the chosen folder.
|
|
596
|
+
:param path: Optional subfolder to list from (e.g., "tests/langs"). If omitted,
|
|
597
|
+
uses self.path if set, otherwise the repo root.
|
|
598
|
+
|
|
599
|
+
:return: A list of repo-relative file paths that match the pattern.
|
|
600
|
+
"""
|
|
601
|
+
headers = self._get_headers()
|
|
602
|
+
base_path = (path or self.path or "").strip("/")
|
|
603
|
+
|
|
604
|
+
if recursive:
|
|
605
|
+
# Use the Git Trees API to fetch all files in one call, then filter.
|
|
606
|
+
tree_url = f"{self.api_url}/git/trees/{self.branch}"
|
|
607
|
+
params = {"recursive": "1"}
|
|
608
|
+
resp = requests.get(tree_url, headers=headers, params=params)
|
|
609
|
+
resp.raise_for_status()
|
|
610
|
+
data = resp.json()
|
|
611
|
+
|
|
612
|
+
files = []
|
|
613
|
+
for entry in data.get("tree", []):
|
|
614
|
+
if entry.get("type") != "blob":
|
|
615
|
+
continue # only files
|
|
616
|
+
entry_path = entry.get("path", "")
|
|
617
|
+
# If a base_path was provided, keep only files under it
|
|
618
|
+
if base_path and not entry_path.startswith(base_path + "/") and entry_path != base_path:
|
|
619
|
+
continue
|
|
620
|
+
# Match pattern against the *file name* (basename)
|
|
621
|
+
if fnmatch.fnmatch(os.path.basename(entry_path), pattern):
|
|
622
|
+
files.append(entry_path)
|
|
623
|
+
return files
|
|
624
|
+
|
|
625
|
+
else:
|
|
626
|
+
# Non-recursive: use the Contents API to list a single directory.
|
|
627
|
+
# If base_path is empty, list the repo root.
|
|
628
|
+
if base_path:
|
|
629
|
+
contents_url = f"{self.contents_url}/{base_path}"
|
|
630
|
+
else:
|
|
631
|
+
contents_url = self.contents_url
|
|
632
|
+
|
|
633
|
+
params = {"ref": self.branch}
|
|
634
|
+
resp = requests.get(contents_url, headers=headers, params=params)
|
|
635
|
+
resp.raise_for_status()
|
|
636
|
+
items = resp.json()
|
|
637
|
+
|
|
638
|
+
# The Contents API returns a dict when the path points to a single file;
|
|
639
|
+
# normalize to a list to simplify handling.
|
|
640
|
+
if isinstance(items, dict):
|
|
641
|
+
items = [items]
|
|
642
|
+
|
|
643
|
+
files = []
|
|
644
|
+
for item in items:
|
|
645
|
+
if item.get("type") == "file":
|
|
646
|
+
name = item.get("name", "")
|
|
647
|
+
if fnmatch.fnmatch(name, pattern):
|
|
648
|
+
# item["path"] is the full repo-relative path we want to return
|
|
649
|
+
files.append(item.get("path", name))
|
|
650
|
+
return files
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _make_parser():
|
|
535
654
|
import argparse
|
|
536
655
|
|
|
537
656
|
parser = argparse.ArgumentParser(description='GitHub Wrapper')
|
|
@@ -562,7 +681,7 @@ def parse_github_args():
|
|
|
562
681
|
'-db', '--download_branch', action='store_true', default=False,
|
|
563
682
|
help='Sets if the branch will be downloaded. In conjunction with path, only the path will be downloaded.')
|
|
564
683
|
|
|
565
|
-
return parser
|
|
684
|
+
return parser
|
|
566
685
|
|
|
567
686
|
|
|
568
687
|
def github_wrapper_main(
|
|
@@ -571,8 +690,8 @@ def github_wrapper_main(
|
|
|
571
690
|
path: str = None,
|
|
572
691
|
target_directory: str = None,
|
|
573
692
|
pat: str = None,
|
|
574
|
-
get_latest_commit_json: bool = False,
|
|
575
693
|
get_latest_commit_message: bool = False,
|
|
694
|
+
get_latest_commit_json: bool = False,
|
|
576
695
|
download_branch: bool = False
|
|
577
696
|
):
|
|
578
697
|
"""
|
|
@@ -610,15 +729,7 @@ def github_wrapper_main(
|
|
|
610
729
|
|
|
611
730
|
|
|
612
731
|
def github_wrapper_main_with_args():
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
branch=args.branch,
|
|
618
|
-
path=args.path,
|
|
619
|
-
target_directory=args.target_directory,
|
|
620
|
-
pat=args.pat,
|
|
621
|
-
get_latest_commit_json=args.get_latest_commit_json,
|
|
622
|
-
get_latest_commit_message=args.get_latest_commit_message,
|
|
623
|
-
download_branch=args.download_branch
|
|
624
|
-
)
|
|
732
|
+
main_parser = _make_parser()
|
|
733
|
+
args = main_parser.parse_args()
|
|
734
|
+
|
|
735
|
+
return github_wrapper_main(**vars(args))
|
|
@@ -30,7 +30,9 @@ class Certificator:
|
|
|
30
30
|
custom_private_key_path: str,
|
|
31
31
|
forwarding_dns_service_ipv4_list___only_for_localhost: list,
|
|
32
32
|
skip_extension_id_list: list,
|
|
33
|
-
tls: bool
|
|
33
|
+
tls: bool,
|
|
34
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool,
|
|
35
|
+
sslkeylog_file_path: str
|
|
34
36
|
):
|
|
35
37
|
self.ca_certificate_name = ca_certificate_name
|
|
36
38
|
self.ca_certificate_filepath = ca_certificate_filepath
|
|
@@ -49,6 +51,9 @@ class Certificator:
|
|
|
49
51
|
forwarding_dns_service_ipv4_list___only_for_localhost)
|
|
50
52
|
self.skip_extension_id_list = skip_extension_id_list
|
|
51
53
|
self.tls = tls
|
|
54
|
+
self.enable_sslkeylogfile_env_to_client_ssl_context: bool = (
|
|
55
|
+
enable_sslkeylogfile_env_to_client_ssl_context)
|
|
56
|
+
self.sslkeylog_file_path: str = sslkeylog_file_path
|
|
52
57
|
|
|
53
58
|
# noinspection PyTypeChecker
|
|
54
59
|
self.certauth_wrapper: CertAuthWrapper = None
|
|
@@ -221,5 +226,10 @@ class Certificator:
|
|
|
221
226
|
|
|
222
227
|
# You need to build new context and exchange the context that being inherited from the main socket,
|
|
223
228
|
# or else the context will receive previous certificate each time.
|
|
224
|
-
sni_received_parameters.ssl_socket.context =
|
|
225
|
-
creator.create_server_ssl_context___load_certificate_and_key(
|
|
229
|
+
sni_received_parameters.ssl_socket.context = (
|
|
230
|
+
creator.create_server_ssl_context___load_certificate_and_key(
|
|
231
|
+
certificate_file_path=sni_server_certificate_file_path, key_file_path=None,
|
|
232
|
+
enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
233
|
+
sslkeylog_file_path=self.sslkeylog_file_path
|
|
234
|
+
)
|
|
235
|
+
)
|
|
@@ -25,7 +25,15 @@ def add_reusable_address_option(socket_instance):
|
|
|
25
25
|
socket_instance.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def create_ssl_context_for_server(
|
|
28
|
+
def create_ssl_context_for_server(
|
|
29
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
|
|
30
|
+
sslkeylog_file_path: str = None,
|
|
31
|
+
allow_legacy: bool = False
|
|
32
|
+
) -> ssl.SSLContext:
|
|
33
|
+
"""
|
|
34
|
+
This function creates the SSL context for the server.
|
|
35
|
+
Meaning that your script will act like a server, and the client will connect to it.
|
|
36
|
+
"""
|
|
29
37
|
# Creating context with SSL certificate and the private key before the socket
|
|
30
38
|
# https://docs.python.org/3/library/ssl.html
|
|
31
39
|
# Creating context for SSL wrapper, specifying "PROTOCOL_TLS_SERVER" will pick the best TLS version protocol for
|
|
@@ -48,6 +56,14 @@ def create_ssl_context_for_server(allow_legacy=False) -> ssl.SSLContext:
|
|
|
48
56
|
ssl_context.verify_mode = ssl.CERT_NONE
|
|
49
57
|
ssl_context.check_hostname = False
|
|
50
58
|
|
|
59
|
+
if enable_sslkeylogfile_env_to_client_ssl_context:
|
|
60
|
+
if sslkeylog_file_path is None:
|
|
61
|
+
sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
|
|
62
|
+
|
|
63
|
+
if not os.path.exists(sslkeylog_file_path):
|
|
64
|
+
open(sslkeylog_file_path, "a").close()
|
|
65
|
+
ssl_context.keylog_filename = sslkeylog_file_path
|
|
66
|
+
|
|
51
67
|
# If you must support old clients that only offer TLS_RSA_* suites under OpenSSL 3:
|
|
52
68
|
if allow_legacy:
|
|
53
69
|
# This enables RSA key exchange and other legacy bits at security level 1
|
|
@@ -64,6 +80,7 @@ def create_ssl_context_for_client(
|
|
|
64
80
|
) -> ssl.SSLContext:
|
|
65
81
|
"""
|
|
66
82
|
This function creates the SSL context for the client.
|
|
83
|
+
This means that your script will act like a client, and will connect to a server.
|
|
67
84
|
The SSL context is created with the "PROTOCOL_TLS_CLIENT" protocol.
|
|
68
85
|
|
|
69
86
|
:param enable_sslkeylogfile_env_to_client_ssl_context: boolean, enables the SSLKEYLOGFILE environment variable
|
|
@@ -83,9 +100,9 @@ def create_ssl_context_for_client(
|
|
|
83
100
|
if sslkeylog_file_path is None:
|
|
84
101
|
sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
|
|
85
102
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
if not os.path.exists(sslkeylog_file_path):
|
|
104
|
+
open(sslkeylog_file_path, "a").close()
|
|
105
|
+
ssl_context.keylog_filename = sslkeylog_file_path
|
|
89
106
|
|
|
90
107
|
current_ciphers = 'AES256-GCM-SHA384:' + ssl._DEFAULT_CIPHERS
|
|
91
108
|
ssl_context.set_ciphers(current_ciphers)
|
|
@@ -191,10 +208,14 @@ def copy_server_ctx_settings(src: ssl.SSLContext, dst: ssl.SSLContext) -> None:
|
|
|
191
208
|
def create_server_ssl_context___load_certificate_and_key(
|
|
192
209
|
certificate_file_path: str,
|
|
193
210
|
key_file_path: str | None,
|
|
194
|
-
inherit_from: ssl.SSLContext | None = None
|
|
211
|
+
inherit_from: ssl.SSLContext | None = None,
|
|
212
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
|
|
213
|
+
sslkeylog_file_path: str = None,
|
|
195
214
|
) -> ssl.SSLContext:
|
|
196
215
|
# Create and set ssl context for server.
|
|
197
|
-
ssl_context: ssl.SSLContext = create_ssl_context_for_server(
|
|
216
|
+
ssl_context: ssl.SSLContext = create_ssl_context_for_server(
|
|
217
|
+
allow_legacy=True, enable_sslkeylogfile_env_to_client_ssl_context=enable_sslkeylogfile_env_to_client_ssl_context,
|
|
218
|
+
sslkeylog_file_path=sslkeylog_file_path)
|
|
198
219
|
|
|
199
220
|
# If you replaced contexts during SNI, copy policy from the old one
|
|
200
221
|
if inherit_from is not None:
|
|
@@ -478,7 +478,7 @@ class DnsServer:
|
|
|
478
478
|
dns_cached_request = False
|
|
479
479
|
# Check if the received data request from client is already in the cache
|
|
480
480
|
if client_data in self.dns_questions_to_answers_cache:
|
|
481
|
-
# message = "!!!
|
|
481
|
+
# message = "!!! Request / Response is already in the dictionary..."
|
|
482
482
|
# self.logger.info(message)
|
|
483
483
|
|
|
484
484
|
# Get the response from the cached answers list
|
|
@@ -561,7 +561,7 @@ class DnsServer:
|
|
|
561
561
|
f'{self.offline_route_ipv6}')
|
|
562
562
|
)
|
|
563
563
|
|
|
564
|
-
message = f"!!!
|
|
564
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
565
565
|
f"{self.offline_route_ipv6}."
|
|
566
566
|
self.logger.info(message)
|
|
567
567
|
|
|
@@ -577,7 +577,7 @@ class DnsServer:
|
|
|
577
577
|
elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
|
|
578
578
|
dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
|
|
579
579
|
|
|
580
|
-
message = f"!!!
|
|
580
|
+
message = f"!!! Request / Response is in offline mode returning: " \
|
|
581
581
|
f"{self.offline_srv_answer}."
|
|
582
582
|
self.logger.info(message)
|
|
583
583
|
elif qtype_string == "ANY":
|
|
@@ -586,7 +586,7 @@ class DnsServer:
|
|
|
586
586
|
self.offline_route_domain)
|
|
587
587
|
)
|
|
588
588
|
|
|
589
|
-
message = f"!!!
|
|
589
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
590
590
|
f"{self.offline_route_domain}."
|
|
591
591
|
self.logger.info(message)
|
|
592
592
|
else:
|
|
@@ -596,7 +596,7 @@ class DnsServer:
|
|
|
596
596
|
" " + self.offline_route_ipv4)
|
|
597
597
|
)
|
|
598
598
|
|
|
599
|
-
message = f"!!!
|
|
599
|
+
message = f"!!! Request / Response is in offline mode returning " \
|
|
600
600
|
f"{self.offline_route_ipv4}."
|
|
601
601
|
self.logger.info(message)
|
|
602
602
|
# Values error means in most cases that you create wrong response
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import socket
|
|
1
2
|
import ssl
|
|
2
3
|
import logging
|
|
3
4
|
from pathlib import Path
|
|
@@ -12,12 +13,12 @@ from . import base
|
|
|
12
13
|
class Sender:
|
|
13
14
|
def __init__(
|
|
14
15
|
self,
|
|
15
|
-
ssl_socket: ssl.SSLSocket,
|
|
16
|
+
ssl_socket: ssl.SSLSocket | socket.socket,
|
|
16
17
|
class_message: bytes,
|
|
17
18
|
logger: logging.Logger = None
|
|
18
19
|
):
|
|
19
20
|
self.class_message: bytes = class_message
|
|
20
|
-
self.ssl_socket: ssl.SSLSocket = ssl_socket
|
|
21
|
+
self.ssl_socket: ssl.SSLSocket | socket.socket = ssl_socket
|
|
21
22
|
|
|
22
23
|
if logger:
|
|
23
24
|
# Create child logger for the provided logger with the module's name.
|
|
@@ -47,7 +47,9 @@ class SNISetup:
|
|
|
47
47
|
tls: bool,
|
|
48
48
|
domain_from_dns_server: str = None,
|
|
49
49
|
skip_extension_id_list: list = None,
|
|
50
|
-
exceptions_logger: loggingw.ExceptionCsvLogger = None
|
|
50
|
+
exceptions_logger: loggingw.ExceptionCsvLogger = None,
|
|
51
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
|
|
52
|
+
sslkeylog_file_path: str = None
|
|
51
53
|
):
|
|
52
54
|
self.ca_certificate_name = ca_certificate_name
|
|
53
55
|
self.ca_certificate_filepath = ca_certificate_filepath
|
|
@@ -74,6 +76,8 @@ class SNISetup:
|
|
|
74
76
|
self.tls = tls
|
|
75
77
|
self.exceptions_logger = exceptions_logger
|
|
76
78
|
self.certificator_instance = None
|
|
79
|
+
self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
|
|
80
|
+
self.sslkeylog_file_path: str = sslkeylog_file_path
|
|
77
81
|
|
|
78
82
|
def wrap_socket_with_ssl_context_server_sni_extended(
|
|
79
83
|
self,
|
|
@@ -82,7 +86,7 @@ class SNISetup:
|
|
|
82
86
|
):
|
|
83
87
|
|
|
84
88
|
# Create SSL Socket to wrap the raw socket with.
|
|
85
|
-
ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(True)
|
|
89
|
+
ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(allow_legacy=True)
|
|
86
90
|
|
|
87
91
|
self.certificator_instance = certificator.Certificator(
|
|
88
92
|
ca_certificate_name=self.ca_certificate_name,
|
|
@@ -101,7 +105,9 @@ class SNISetup:
|
|
|
101
105
|
forwarding_dns_service_ipv4_list___only_for_localhost=(
|
|
102
106
|
self.forwarding_dns_service_ipv4_list___only_for_localhost),
|
|
103
107
|
skip_extension_id_list=self.skip_extension_id_list,
|
|
104
|
-
tls=self.tls
|
|
108
|
+
tls=self.tls,
|
|
109
|
+
enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
110
|
+
sslkeylog_file_path=self.sslkeylog_file_path
|
|
105
111
|
)
|
|
106
112
|
|
|
107
113
|
# Add SNI callback function to the SSL context.
|
|
@@ -160,7 +166,10 @@ class SNISetup:
|
|
|
160
166
|
certificator_instance=self.certificator_instance,
|
|
161
167
|
domain_from_dns_server=self.domain_from_dns_server,
|
|
162
168
|
default_certificate_domain_list=self.default_certificate_domain_list,
|
|
163
|
-
exceptions_logger=self.exceptions_logger
|
|
169
|
+
exceptions_logger=self.exceptions_logger,
|
|
170
|
+
enable_sslkeylogfile_env_to_client_ssl_context=(
|
|
171
|
+
self.certificator_instance.enable_sslkeylogfile_env_to_client_ssl_context),
|
|
172
|
+
sslkeylog_file_path=self.certificator_instance.sslkeylog_file_path)
|
|
164
173
|
ssl_context.set_servername_callback(
|
|
165
174
|
sni_handler_instance.setup_sni_callback(print_kwargs=print_kwargs))
|
|
166
175
|
|
|
@@ -178,7 +187,9 @@ class SNIHandler:
|
|
|
178
187
|
certificator_instance: certificator.Certificator,
|
|
179
188
|
domain_from_dns_server: str,
|
|
180
189
|
default_certificate_domain_list: list,
|
|
181
|
-
exceptions_logger: loggingw.ExceptionCsvLogger
|
|
190
|
+
exceptions_logger: loggingw.ExceptionCsvLogger,
|
|
191
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool,
|
|
192
|
+
sslkeylog_file_path: str
|
|
182
193
|
):
|
|
183
194
|
self.sni_use_default_callback_function_extended = sni_use_default_callback_function_extended
|
|
184
195
|
self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
|
|
@@ -187,6 +198,8 @@ class SNIHandler:
|
|
|
187
198
|
self.domain_from_dns_server: str = domain_from_dns_server
|
|
188
199
|
self.default_certificate_domain_list = default_certificate_domain_list
|
|
189
200
|
self.exceptions_logger = exceptions_logger
|
|
201
|
+
self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
|
|
202
|
+
self.sslkeylog_file_path: str = sslkeylog_file_path
|
|
190
203
|
|
|
191
204
|
# noinspection PyTypeChecker
|
|
192
205
|
self.sni_received_parameters: SNIReceivedParameters = None
|
|
@@ -325,7 +338,9 @@ class SNIHandler:
|
|
|
325
338
|
creator.create_server_ssl_context___load_certificate_and_key(
|
|
326
339
|
default_server_certificate_path,
|
|
327
340
|
None,
|
|
328
|
-
inherit_from=self.sni_received_parameters.ssl_socket.context
|
|
341
|
+
inherit_from=self.sni_received_parameters.ssl_socket.context,
|
|
342
|
+
enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
343
|
+
sslkeylog_file_path=self.sslkeylog_file_path
|
|
329
344
|
)
|
|
330
345
|
)
|
|
331
346
|
else:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import multiprocessing
|
|
2
2
|
import threading
|
|
3
|
-
import time
|
|
4
3
|
|
|
5
4
|
import select
|
|
6
5
|
from typing import Literal, Union, Callable, Any
|
|
@@ -80,6 +79,8 @@ class SocketWrapper:
|
|
|
80
79
|
statistics_logger_queue: multiprocessing.Queue = None,
|
|
81
80
|
exceptions_logger_name: str = 'SocketWrapperExceptions',
|
|
82
81
|
exceptions_logger_queue: multiprocessing.Queue = None,
|
|
82
|
+
enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
|
|
83
|
+
sslkeylog_file_path: str = None,
|
|
83
84
|
print_kwargs: dict = None,
|
|
84
85
|
):
|
|
85
86
|
"""
|
|
@@ -173,6 +174,12 @@ class SocketWrapper:
|
|
|
173
174
|
:param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
|
|
174
175
|
:param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
|
|
175
176
|
multiprocessing. You need to start the logger listener in the main process to handle the queue.
|
|
177
|
+
:param enable_sslkeylogfile_env_to_client_ssl_context: boolean, if True, each client SSL context
|
|
178
|
+
that will be created by the SocketWrapper will have save the SSL handshake keys to the file
|
|
179
|
+
defined in 'sslkeylog_file_path' parameter.
|
|
180
|
+
:param sslkeylog_file_path: string, path to file where SSL handshake keys will be saved.
|
|
181
|
+
If not provided and 'enable_sslkeylogfile_env_to_client_ssl_context' is True, then
|
|
182
|
+
the environment variable 'SSLKEYLOGFILE' will be used.
|
|
176
183
|
:param print_kwargs: dict, additional arguments to pass to the print function.
|
|
177
184
|
"""
|
|
178
185
|
|
|
@@ -208,6 +215,9 @@ class SocketWrapper:
|
|
|
208
215
|
self.ssh_script_to_execute = ssh_script_to_execute
|
|
209
216
|
self.forwarding_dns_service_ipv4_list___only_for_localhost = (
|
|
210
217
|
forwarding_dns_service_ipv4_list___only_for_localhost)
|
|
218
|
+
self.enable_sslkeylogfile_env_to_client_ssl_context: bool = (
|
|
219
|
+
enable_sslkeylogfile_env_to_client_ssl_context)
|
|
220
|
+
self.sslkeylog_file_path: str = sslkeylog_file_path
|
|
211
221
|
self.print_kwargs: dict = print_kwargs
|
|
212
222
|
|
|
213
223
|
self.socket_object = None
|
|
@@ -616,7 +626,9 @@ class SocketWrapper:
|
|
|
616
626
|
forwarding_dns_service_ipv4_list___only_for_localhost=(
|
|
617
627
|
self.forwarding_dns_service_ipv4_list___only_for_localhost),
|
|
618
628
|
tls=is_tls,
|
|
619
|
-
exceptions_logger=self.exceptions_logger
|
|
629
|
+
exceptions_logger=self.exceptions_logger,
|
|
630
|
+
enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
|
|
631
|
+
sslkeylog_file_path=self.sslkeylog_file_path
|
|
620
632
|
)
|
|
621
633
|
|
|
622
634
|
ssl_client_socket, accept_error_message = \
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=2_vm_IGYQueXrDSwBdgjXn7kHxyeKqTdQmDo1JOD4_0,122
|
|
2
2
|
atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
|
|
3
3
|
atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
|
|
4
4
|
atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
|
|
@@ -126,11 +126,11 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
|
|
|
126
126
|
atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
127
|
atomicshop/mitm/config_static.py,sha256=KzO8DjZjRHfkQMYSIGTkW4jLNPzMR8visTqs1H6ZQ-U,8926
|
|
128
128
|
atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
|
|
129
|
-
atomicshop/mitm/connection_thread_worker.py,sha256=
|
|
129
|
+
atomicshop/mitm/connection_thread_worker.py,sha256=NPHizpPJOaYjP05EEGxEOOKHhgbe4CSzUuk7WTPgZd4,38858
|
|
130
130
|
atomicshop/mitm/import_config.py,sha256=7aLfKqflc3ZnzKc2_Y4T0eenzQpKG94M0r-PaVwF99M,18881
|
|
131
131
|
atomicshop/mitm/initialize_engines.py,sha256=qzz5jzh_lKC03bI1w5ebngVXo1K-RV3poAyW-nObyqo,11042
|
|
132
132
|
atomicshop/mitm/message.py,sha256=CDhhm4BTuZE7oNZCjvIZ4BuPOW4MuIzQLOg91hJaxDI,3065
|
|
133
|
-
atomicshop/mitm/mitm_main.py,sha256=
|
|
133
|
+
atomicshop/mitm/mitm_main.py,sha256=i0YcLDKAYH1aUS_Rf7IbwXiSQzndsdOY_JuYTvTiTN8,40453
|
|
134
134
|
atomicshop/mitm/recs_files.py,sha256=tv8XFhYZMkBv4DauvpiAdPgvSo0Bcm1CghnmwO7dx8M,5018
|
|
135
135
|
atomicshop/mitm/shared_functions.py,sha256=0lzeyINd44sVEfFbahJxQmz6KAMWbYrW5ou3UYfItvw,1777
|
|
136
136
|
atomicshop/mitm/statistic_analyzer.py,sha256=EC9g21ocOsFzNfntV-nZHSGtrS1-Kxb0QDSGWS5FuNA,28942
|
|
@@ -140,7 +140,7 @@ atomicshop/mitm/engines/create_module_template_main_example.py,sha256=LeQ44Rp2Gi
|
|
|
140
140
|
atomicshop/mitm/engines/__parent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
141
141
|
atomicshop/mitm/engines/__parent/parser___parent.py,sha256=HHaCXhScl3OlPjz6eUxsDpJaZyk6BNuDMc9xCkeo2Ws,661
|
|
142
142
|
atomicshop/mitm/engines/__parent/recorder___parent.py,sha256=D99cbpMneY9STSAPETa6eIxyfs_Q9etRYxm2dosA-DI,6203
|
|
143
|
-
atomicshop/mitm/engines/__parent/requester___parent.py,sha256
|
|
143
|
+
atomicshop/mitm/engines/__parent/requester___parent.py,sha256=-S817ERzs1pdY2rXiQBnG4eB4sm2jsy2wycpvuiDlpI,5092
|
|
144
144
|
atomicshop/mitm/engines/__parent/responder___parent.py,sha256=mtiS_6ej9nxT9UhAQR4ftMqnqL-j_kO3u8KEaoEaI9k,9495
|
|
145
145
|
atomicshop/mitm/engines/__reference_general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
146
146
|
atomicshop/mitm/engines/__reference_general/parser___reference_general.py,sha256=57MEPZMAjTO6xBDZ-yt6lgGJyqRrP0Do5Gk_cgCiPns,2998
|
|
@@ -187,7 +187,7 @@ atomicshop/wrappers/astw.py,sha256=VkYfkfyc_PJLIOxByT6L7B8uUmKY6-I8XGZl4t_z828,4
|
|
|
187
187
|
atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK_tJuSgU,22800
|
|
188
188
|
atomicshop/wrappers/cryptographyw.py,sha256=QEUpDn8vUvMg3ADz6-4oC2kbDNC_woDlw7C0zU7qFVM,14233
|
|
189
189
|
atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
|
|
190
|
-
atomicshop/wrappers/githubw.py,sha256=
|
|
190
|
+
atomicshop/wrappers/githubw.py,sha256=95RAbxomp4GA9-7B1pk6q3aYOmrCO0vkHyhSPgr3n3Q,31924
|
|
191
191
|
atomicshop/wrappers/netshw.py,sha256=8WE_576XiiHykwFuE-VkCx5CydMpFlztX4frlEteCtI,6350
|
|
192
192
|
atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc,1025
|
|
193
193
|
atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
|
|
@@ -298,24 +298,24 @@ atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py,sha256=Ng5pbWeQBNcPqfFuxHEIdkIU
|
|
|
298
298
|
atomicshop/wrappers/socketw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
299
299
|
atomicshop/wrappers/socketw/accepter.py,sha256=4I9ORugRDvwaqSzm_gWSjZnRwQGY8hDTlCdsYHwH_ZE,2377
|
|
300
300
|
atomicshop/wrappers/socketw/base.py,sha256=EcosGkD8VzgBY3GeIHDSG29ThQfXwg3-GQPmBTAqTdw,3048
|
|
301
|
-
atomicshop/wrappers/socketw/certificator.py,sha256=
|
|
302
|
-
atomicshop/wrappers/socketw/creator.py,sha256
|
|
303
|
-
atomicshop/wrappers/socketw/dns_server.py,sha256=
|
|
301
|
+
atomicshop/wrappers/socketw/certificator.py,sha256=mA78HXg6j_WKQ7D26Ue66QKA9y1Iu-EWsnc6cI6RC8w,12976
|
|
302
|
+
atomicshop/wrappers/socketw/creator.py,sha256=-Xnc59CLHfBEmncwjIhyHBG-p07uNeSxTzr2S1LW7a4,17200
|
|
303
|
+
atomicshop/wrappers/socketw/dns_server.py,sha256=UHq1a3NVdOrclEOOQIe-wNtIgbF8DFeNXsobvtoM1U8,55961
|
|
304
304
|
atomicshop/wrappers/socketw/exception_wrapper.py,sha256=_p98OdOaKYSMqJ23pHLXBUA7NkbVmpgqcSJAdWr6wwc,7560
|
|
305
305
|
atomicshop/wrappers/socketw/get_process.py,sha256=aJC-_qFUv3NgWCSUzDI72E4z8_-VTZE9NVZ0CwUoNlM,5698
|
|
306
306
|
atomicshop/wrappers/socketw/receiver.py,sha256=9B3MvcDqr4C3x2fsnjG5SQognd1wRqsBgikxZa0wXG8,8243
|
|
307
|
-
atomicshop/wrappers/socketw/sender.py,sha256=
|
|
308
|
-
atomicshop/wrappers/socketw/sni.py,sha256=
|
|
307
|
+
atomicshop/wrappers/socketw/sender.py,sha256=5ecHUlz4Sxt4oWevBFfy33jQLRXmmVLOF34njfvSbxY,4801
|
|
308
|
+
atomicshop/wrappers/socketw/sni.py,sha256=XlTmNIdJYjW_hkF9eCXu7GdhSJYJvKDmTiPrLV1-TdQ,19374
|
|
309
309
|
atomicshop/wrappers/socketw/socket_client.py,sha256=WWIiCxUX9irN9aWzJ6-1xrXNB_iv_diq3ha1yrWsNGU,22671
|
|
310
310
|
atomicshop/wrappers/socketw/socket_server_tester.py,sha256=Qobmh4XV8ZxLUaw-eW4ESKAbeSLecCKn2OWFzMhadk0,6420
|
|
311
|
-
atomicshop/wrappers/socketw/socket_wrapper.py,sha256=
|
|
311
|
+
atomicshop/wrappers/socketw/socket_wrapper.py,sha256=KdwZvATt7iEC2R0to9jcE0kBtG1cjcZwmF_cMn5fFms,43616
|
|
312
312
|
atomicshop/wrappers/socketw/ssl_base.py,sha256=62-hPm7zla1rh3m_WvDnXqKH-sDUTdiRptD8STCkgdk,2313
|
|
313
313
|
atomicshop/wrappers/socketw/statistics_csv.py,sha256=_gA8bMX6Sw_UCXKi2y9wNAwlqifgExgDGfQIa9pFxQA,5543
|
|
314
314
|
atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
315
315
|
atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
|
|
316
316
|
atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
|
|
317
|
-
atomicshop-3.
|
|
318
|
-
atomicshop-3.
|
|
319
|
-
atomicshop-3.
|
|
320
|
-
atomicshop-3.
|
|
321
|
-
atomicshop-3.
|
|
317
|
+
atomicshop-3.4.1.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
318
|
+
atomicshop-3.4.1.dist-info/METADATA,sha256=H6vq6tDZmmoszao5uweXIMSDyVq0QhP4_-cJ4Ow_PDk,9317
|
|
319
|
+
atomicshop-3.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
320
|
+
atomicshop-3.4.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
321
|
+
atomicshop-3.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|