nopasaran 0.2.95__py3-none-any.whl → 0.2.96__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.
@@ -25,6 +25,9 @@ from nopasaran.primitives.action_primitives.client_echo_primitives import Client
25
25
  from nopasaran.primitives.action_primitives.probing_primitives import PortProbingPrimitives
26
26
  from nopasaran.primitives.action_primitives.replay_primitives import ReplayPrimitives
27
27
  from nopasaran.primitives.action_primitives.http_simple_client_primitives import HTTPSimpleClientPrimitives
28
+ from nopasaran.primitives.action_primitives.tcp_dns_request_primitives import TCPDNSRequestPrimitives
29
+ from nopasaran.primitives.action_primitives.tcp_dns_response_primitives import TCPDNSResponsePrimitives
30
+
28
31
 
29
32
  class ActionPrimitives(Primitives):
30
33
  """
@@ -56,5 +59,7 @@ class ActionPrimitives(Primitives):
56
59
  ClientEchoPrimitives,
57
60
  PortProbingPrimitives,
58
61
  ReplayPrimitives,
59
- HTTPSimpleClientPrimitives
62
+ HTTPSimpleClientPrimitives,
63
+ TCPDNSResponsePrimitives,
64
+ TCPDNSRequestPrimitives
60
65
  ]
@@ -0,0 +1,29 @@
1
+ from nopasaran.decorators import parsing_decorator
2
+ from nopasaran.definitions.events import EventNames
3
+ import nopasaran.utils as utils
4
+
5
+ class TCPDNSRequestPrimitives:
6
+ @staticmethod
7
+ @parsing_decorator(input_args=4, output_args=1)
8
+ def make_tcp_dns_query(inputs, outputs, state_machine):
9
+ """
10
+ Make a DNS query over TCP with user-defined domain and query type.
11
+ inputs: [domain, query_type, server_ip, server_port]
12
+ outputs: [dns_response_dict]
13
+ """
14
+ domain = state_machine.get_variable_value(inputs[0])
15
+ query_type = state_machine.get_variable_value(inputs[1])
16
+ server_ip = state_machine.get_variable_value(inputs[2])
17
+ server_port = int(state_machine.get_variable_value(inputs[3]))
18
+
19
+ # Call utility function with provided parameters
20
+ result = utils.send_tcp_dns_query(server_ip, server_port, domain, query_type)
21
+
22
+ # Store result and trigger events accordingly
23
+ if not result or result.get("response") is None:
24
+ state_machine.set_variable_value(outputs[0], {"received": None})
25
+ state_machine.trigger_event(EventNames.REQUEST_ERROR.name)
26
+ else:
27
+ state_machine.set_variable_value(outputs[0], {"received": result})
28
+ state_machine.trigger_event(EventNames.RESPONSE_RECEIVED.name)
29
+
@@ -0,0 +1,40 @@
1
+ from nopasaran.decorators import parsing_decorator
2
+ from nopasaran.tools.tcp_dns_socket_server import TCPDNSSocketServer
3
+
4
+ class TCPDNSResponsePrimitives:
5
+
6
+ @staticmethod
7
+ @parsing_decorator(input_args=0, output_args=1)
8
+ def create_tcp_dns_server(inputs, outputs, state_machine):
9
+ server = TCPDNSSocketServer()
10
+ state_machine.set_variable_value(outputs[0], server)
11
+
12
+ @staticmethod
13
+ @parsing_decorator(input_args=2, output_args=0)
14
+ def start_tcp_dns_server(inputs, outputs, state_machine):
15
+ server = state_machine.get_variable_value(inputs[0])
16
+ port = int(state_machine.get_variable_value(inputs[1]))
17
+ server.start(port)
18
+
19
+ @staticmethod
20
+ @parsing_decorator(input_args=4, output_args=1)
21
+ def wait_and_respond_tcp_dns_query(inputs, outputs, state_machine):
22
+ """
23
+ Wait for a DNS query and respond based on provided spec.
24
+ Inputs: [server_instance, port, timeout, response_spec]
25
+ Outputs: [result_dict]
26
+ Example response_spec:
27
+ {"type": "CNAME", "value": "safe.com", "qname": "blocked.com."}
28
+ """
29
+ server = state_machine.get_variable_value(inputs[0])
30
+ timeout = int(state_machine.get_variable_value(inputs[2]))
31
+ response_spec = state_machine.get_variable_value(inputs[3]) # can be None
32
+
33
+ result, _ = server.wait_for_query(timeout, response_spec)
34
+ state_machine.set_variable_value(outputs[0], result)
35
+
36
+ @staticmethod
37
+ @parsing_decorator(input_args=1, output_args=0)
38
+ def close_tcp_dns_server(inputs, outputs, state_machine):
39
+ server = state_machine.get_variable_value(inputs[0])
40
+ server.close()
@@ -0,0 +1,170 @@
1
+ import socket
2
+ import struct
3
+ import select
4
+ import time
5
+ from dnslib import DNSRecord, RR, QTYPE, A, CNAME, MX, TXT, NS, SOA, PTR, AAAA, SRV, DS, RRSIG, NSEC, DNSKEY
6
+ from nopasaran.definitions.events import EventNames
7
+
8
+ class TCPDNSSocketServer:
9
+ def __init__(self):
10
+ self.sock = None
11
+
12
+ def start(self, port):
13
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
14
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
15
+ self.sock.bind(('', port))
16
+ self.sock.listen(5)
17
+ return EventNames.SERVER_STARTED.name, f"TCP DNS server started on port {port}"
18
+
19
+ def wait_for_query(self, timeout, response_spec=None):
20
+ timeout = float(timeout)
21
+ start_time = time.time()
22
+ self.sock.setblocking(False)
23
+
24
+ print(f"[Server] Waiting for connections on port {self.sock.getsockname()[1]} with timeout {timeout} seconds")
25
+
26
+ while True:
27
+ remaining_time = timeout - (time.time() - start_time)
28
+ print(f"[Server] Remaining time: {remaining_time:.2f} seconds")
29
+ if remaining_time <= 0:
30
+ print("[Server] Timeout reached with no connection.")
31
+ return {"received": None}, EventNames.TIMEOUT.name
32
+
33
+ ready, _, _ = select.select([self.sock], [], [], remaining_time)
34
+ if ready:
35
+ print("[Server] Socket is ready, accepting...")
36
+ client_sock, client_addr = self.sock.accept()
37
+ print(f"[Server] Accepted connection from {client_addr}")
38
+
39
+ try:
40
+ # Set client socket read timeout
41
+ client_sock.settimeout(5)
42
+ length_data = client_sock.recv(2)
43
+ print(f"[Server] Received length_data: {length_data}")
44
+
45
+ if len(length_data) < 2:
46
+ print("[Server] Incomplete length_data")
47
+ return {"received": None}, EventNames.ERROR.name
48
+
49
+ expected_length = struct.unpack("!H", length_data)[0]
50
+ print(f"[Server] Expecting {expected_length} bytes of query data")
51
+
52
+ request_data = b""
53
+ receive_start_time = time.time()
54
+ receive_timeout = 5 # seconds
55
+
56
+ while len(request_data) < expected_length:
57
+ # Check elapsed time to prevent infinite waiting
58
+ if time.time() - receive_start_time > receive_timeout:
59
+ print("[Server] Timeout while receiving DNS query data")
60
+ return {"received": None}, EventNames.TIMEOUT.name
61
+
62
+ try:
63
+ chunk = client_sock.recv(expected_length - len(request_data))
64
+ if not chunk:
65
+ print("[Server] Connection closed before full query received")
66
+ return {"received": None}, EventNames.ERROR.name
67
+ request_data += chunk
68
+ print(f"[Server] Received {len(request_data)}/{expected_length} bytes")
69
+ except socket.timeout:
70
+ print("[Server] Socket recv() timed out")
71
+ return {"received": None}, EventNames.TIMEOUT.name
72
+
73
+ if not request_data:
74
+ print("[Server] No request data received")
75
+ return {"received": None}, EventNames.ERROR.name
76
+
77
+ print("[Server] Parsing DNS query...")
78
+ parsed_query = DNSRecord.parse(request_data)
79
+ print(f"[Server] Parsed query: {parsed_query.toZone()}")
80
+
81
+ print("[Server] Building DNS response...")
82
+ response = self.build_response(parsed_query, response_spec)
83
+
84
+ print("[Server] Sending DNS response...")
85
+ self.send_dns_response(client_sock, response)
86
+ print("[Server] DNS response sent successfully.")
87
+
88
+ return {
89
+ "received": parsed_query.toZone(),
90
+ "client_address": client_addr
91
+ }, EventNames.REQUEST_RECEIVED.name
92
+
93
+ finally:
94
+ print("[Server] Closing client socket")
95
+ client_sock.close()
96
+
97
+
98
+
99
+ def build_response(self, query_record, response_spec=None):
100
+ qname = str(query_record.q.qname)
101
+ qtype = query_record.q.qtype
102
+ response_qname = response_spec.get("qname") if response_spec and response_spec.get("qname") else qname
103
+ response_type = response_spec.get("type").upper() if response_spec and response_spec.get("type") else QTYPE[qtype].name
104
+ response_value = response_spec.get("value") if response_spec else None
105
+
106
+ response = query_record.reply()
107
+
108
+ handlers = {
109
+ "A": lambda: A(response_value or "127.0.0.1"),
110
+ "CNAME": lambda: CNAME(response_value or response_qname),
111
+ "MX": lambda: MX(response_value or f"mail.{response_qname}", preference=10),
112
+ "TXT": lambda: TXT(response_value or f"dummy record for {response_qname}"),
113
+ "NS": lambda: NS(response_value or f"ns1.{response_qname}"),
114
+ "SOA": lambda: SOA(response_value or f"ns1.{response_qname}", f"admin.{response_qname}", (2024051801, 3600, 3600, 3600, 3600)),
115
+ "PTR": lambda: PTR(response_value or f"ptr.{response_qname}"),
116
+ "AAAA": lambda: AAAA(response_value or "::1"),
117
+ "SRV": lambda: self._parse_srv(response_value or f"service.{response_qname},80,0,0"),
118
+ "DS": lambda: DS(12345, 1, 1, bytes(response_value or f"abcdef{response_qname}", 'utf-8')),
119
+ "RRSIG": lambda: RRSIG(1, 1, 0, 3600, 0, 0, 0, response_value or f"signer.{response_qname}", b"signature"),
120
+ "NSEC": lambda: NSEC(response_value or f"next.{response_qname}", []),
121
+ "DNSKEY": lambda: DNSKEY(256, 3, 8, bytes(response_value or f"publickey{response_qname}", 'utf-8')),
122
+ "ANY": lambda: A(response_value or "127.0.0.1")
123
+ }
124
+
125
+ handler = handlers.get(response_type)
126
+ if not handler:
127
+ print(f"[Server Error] No handler found for response_type: {response_type}")
128
+ return query_record.reply()
129
+
130
+ reverse_qtype = {QTYPE[k]: k for k in QTYPE if isinstance(k, int)}
131
+ rtype = reverse_qtype.get(response_type)
132
+ if rtype is None:
133
+ print(f"[Server Error] Unsupported response_type: {response_type}")
134
+ return query_record.reply()
135
+
136
+ try:
137
+ print(f"[Server] Calling handler for type {response_type}")
138
+ rdata = handler()
139
+ print(f"[Server] Handler produced rdata: {rdata}")
140
+ except Exception as e:
141
+ print(f"[Server Error] Handler for {response_type} failed: {e}")
142
+ return query_record.reply()
143
+
144
+ response.add_answer(RR(rname=response_qname, rtype=rtype, rclass=1, ttl=60, rdata=rdata))
145
+ return response
146
+
147
+
148
+
149
+ def _parse_srv(self, value):
150
+ try:
151
+ target, port, priority, weight = value.split(",")
152
+ return SRV(int(priority), int(weight), int(port), target)
153
+ except Exception:
154
+ return SRV(0, 0, 80, "service.example.com")
155
+
156
+ def send_dns_response(self, client_sock, dns_record):
157
+ response_bytes = dns_record.pack()
158
+ length_prefix = struct.pack("!H", len(response_bytes))
159
+ try:
160
+ client_sock.sendall(length_prefix + response_bytes)
161
+ return EventNames.RESPONSE_SENT.name
162
+ except Exception as e:
163
+ print(f"Failed to send DNS response: {e}")
164
+ return EventNames.ERROR.name
165
+
166
+ def close(self):
167
+ if self.sock:
168
+ self.sock.close()
169
+ self.sock = None
170
+ return EventNames.CONNECTION_CLOSED.name
nopasaran/utils.py CHANGED
@@ -4,6 +4,9 @@ import random
4
4
  import socket
5
5
  import select
6
6
  import ssl
7
+ import struct
8
+ from dnslib import DNSRecord, QTYPE
9
+
7
10
 
8
11
 
9
12
  from scapy.all import IP, TCP, UDP, ICMP, Raw
@@ -391,3 +394,76 @@ def group_ports(ports):
391
394
  return result
392
395
 
393
396
 
397
+
398
+
399
+ def send_tcp_dns_query(server_ip, server_port, domain, query_type="A"):
400
+ dnsatypes = {
401
+ 1: "A", 2: "NS", 5: "CNAME", 6: "SOA", 12: "PTR", 15: "MX",
402
+ 16: "TXT", 28: "AAAA", 33: "SRV", 43: "DS",
403
+ 46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 255: "ANY"
404
+ }
405
+
406
+ result = {"query": None, "response": None, "error": None}
407
+ supported_types = ', '.join([f"{v}({k})" for k, v in dnsatypes.items()])
408
+ lookup_by_name = {v.upper(): k for k, v in dnsatypes.items()}
409
+ query_type_str = str(query_type).strip().upper()
410
+
411
+ print(f"[Debug] Received query_type: {query_type_str}")
412
+
413
+ # Validate query type
414
+ if query_type_str.isdigit():
415
+ qtype = int(query_type_str)
416
+ if qtype not in dnsatypes:
417
+ result["error"] = f"Unsupported numeric query type: {query_type}.\nSupported types are: {supported_types}"
418
+ print("[Error]", result["error"])
419
+ return result
420
+ else:
421
+ qtype = lookup_by_name.get(query_type_str)
422
+ if qtype is None:
423
+ result["error"] = f"Unsupported string query type: {query_type}.\nSupported types are: {supported_types}"
424
+ print("[Error]", result["error"])
425
+ return result
426
+
427
+ try:
428
+ print(f"[Debug] Building DNS query for domain {domain} with type {query_type_str} ({qtype})")
429
+ dns_query = DNSRecord.question(domain, qtype=query_type_str)
430
+ dns_query.header.id = random.randint(0, 65535)
431
+ query_packet = dns_query.pack()
432
+ result["query"] = dns_query.toZone()
433
+
434
+ print("[Debug] Opening socket")
435
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
436
+ sock.bind(('', 0))
437
+ sock.settimeout(2)
438
+
439
+ print(f"[Debug] Connecting to {server_ip}:{server_port}")
440
+ sock.connect((server_ip, server_port))
441
+
442
+ length_prefix = struct.pack("!H", len(query_packet))
443
+ print("[Debug] Sending DNS query")
444
+ sock.sendall(length_prefix + query_packet)
445
+
446
+ print("[Debug] Waiting for response")
447
+ length_data = sock.recv(2)
448
+ if len(length_data) < 2:
449
+ result["error"] = "Incomplete length prefix"
450
+ print("[Error]", result["error"])
451
+ return result
452
+
453
+ expected_length = struct.unpack("!H", length_data)[0]
454
+ response_data = b""
455
+ while len(response_data) < expected_length:
456
+ chunk = sock.recv(expected_length - len(response_data))
457
+ if not chunk:
458
+ break
459
+ response_data += chunk
460
+
461
+ print("[Debug] Received response data")
462
+ parsed_response = DNSRecord.parse(response_data)
463
+ result["response"] = parsed_response.to_dict()
464
+ return result
465
+
466
+ except Exception as e:
467
+ result["error"] = f"Exception occurred: {str(e)}"
468
+ print("[Error]", result["error"])
469
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nopasaran
3
- Version: 0.2.95
3
+ Version: 0.2.96
4
4
  Summary: NoPASARAN is an advanced network tool designed to detect, fingerprint, and locate network middleboxes in a unified framework.
5
5
  Home-page: https://github.com/BenIlies/NoPASARAN
6
6
  Author: Ilies Benhabbour
@@ -20,6 +20,7 @@ Requires-Dist: scapy
20
20
  Requires-Dist: h2
21
21
  Requires-Dist: overload
22
22
  Requires-Dist: dnspython
23
+ Requires-Dist: dnslib
23
24
 
24
25
  # NoPASARAN
25
26
 
@@ -2,7 +2,7 @@ nopasaran/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  nopasaran/__main__.py,sha256=9dY9M5gbwdR74rBezoSAvQPO3acAVHFA5Kov9KkrXO8,2461
3
3
  nopasaran/decorators.py,sha256=4QcUtZAMYZqNWwv7OsIAhHcec8C_MfxThA8kmUXw1Xo,2941
4
4
  nopasaran/http_2_utils.py,sha256=eQL8uG2USCN5ab2brrYtxcTnYk-g3s_b7v4bZ3YdA-Y,26324
5
- nopasaran/utils.py,sha256=cklYdlyv7y7f0yBr4i1HbHjIuqCF-97nF0LuDviCHUo,11234
5
+ nopasaran/utils.py,sha256=MmRiaBkDX655jXTOCgVnCZwkRI8raDQysTaAL6P0BMM,14206
6
6
  nopasaran/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  nopasaran/controllers/controller.py,sha256=kh6_ByvSMRiz8o8DgfR00WfylmClVZKdHe1OIcnltp8,7326
8
8
  nopasaran/controllers/factory.py,sha256=e1c6oea4ePiSZcQuAUn-515kjgmBIOqFXQ_78huEXHw,1287
@@ -28,7 +28,7 @@ nopasaran/parsers/state_machine_parser.py,sha256=suV_1dtGe3tEdqccHL4WItariHCYLVH
28
28
  nopasaran/primitives/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  nopasaran/primitives/primitives.py,sha256=Qgx56p1lCLuzFL-udz777f1ttUYakWluID7G5kpQJc8,371
30
30
  nopasaran/primitives/action_primitives/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- nopasaran/primitives/action_primitives/action_primitives.py,sha256=K4yu9W25BM6BGyjCUBCeBTtb7cqpoqGs244oc_poMjI,3340
31
+ nopasaran/primitives/action_primitives/action_primitives.py,sha256=JNYnN8BiWk8KuCMwcKbEGv-IYOycobJQ7sStZE4Ge98,3614
32
32
  nopasaran/primitives/action_primitives/certificate_primitives.py,sha256=3PDUDTYkTvbnIW5anPuG4igNyPj6YReo4lw69JvJbB0,13480
33
33
  nopasaran/primitives/action_primitives/client_echo_primitives.py,sha256=2Qupem0N4miGePilnm0-nsVbZUUS6BpME2JhKlU-32E,3532
34
34
  nopasaran/primitives/action_primitives/control_channel_primitives.py,sha256=_OMgxLLtaK0vvobwhPvSAJhYCOgn4optcGhXOdgh1Q4,11281
@@ -51,6 +51,8 @@ nopasaran/primitives/action_primitives/probing_primitives.py,sha256=tuEWcz1K9Mxj
51
51
  nopasaran/primitives/action_primitives/replay_primitives.py,sha256=pRMq6eZsm82rf049CggWrxTJCDu0xxYpSXxBLHzrFlY,4937
52
52
  nopasaran/primitives/action_primitives/server_echo_primitives.py,sha256=CSw99moDTUqWnwJ19kcLmsRsUZfsysWk50N8pJWqcbo,6660
53
53
  nopasaran/primitives/action_primitives/signaling_primitive.py,sha256=4gmG3r5lXX1KsicPwepGTrnouNpc6j0Bkf8pV5jJTmw,6634
54
+ nopasaran/primitives/action_primitives/tcp_dns_request_primitives.py,sha256=sJGSc0bl7LtE_dFE0ezj1HWvY-cWEIUBQuXaQAVk2K0,1328
55
+ nopasaran/primitives/action_primitives/tcp_dns_response_primitives.py,sha256=Ogf9idWfMgoo3oXFSzyG358jsgDybdti7JXHF-xVMmo,1657
54
56
  nopasaran/primitives/action_primitives/tcp_primitives.py,sha256=H4lpqeL5uhqqwC0RYrelO0BlrTocrfGl4dbd6FUi5FI,16909
55
57
  nopasaran/primitives/action_primitives/timing_primitives.py,sha256=hq4Q2_9nbL9ihf5TRAMRPWZuybqP4CXZxfstelAF6R8,980
56
58
  nopasaran/primitives/action_primitives/tls_primitives.py,sha256=UZ-bwYBDCVxPrZjodGTTdICYGjfA9WPymIAqaOeaZYI,6163
@@ -72,10 +74,11 @@ nopasaran/tools/http_2_socket_base.py,sha256=KDfkU4Nr_TXxqzp1-ZHec_UawfDZ5oIIj-J
72
74
  nopasaran/tools/http_2_socket_client.py,sha256=5mmc8FuT2dNVAihbMdvyytVwME_xSc7mez_jwU3YZOA,6256
73
75
  nopasaran/tools/http_2_socket_server.py,sha256=8W-bxmdoGyK5moxpC87V1EGuZ9jJpIKhokvZrDV84kk,5551
74
76
  nopasaran/tools/https_1_socket_server.py,sha256=0mJ_0zCC-7rtvyjehTnm1QJxWx8R4YHkAWRHm2msvL4,9217
77
+ nopasaran/tools/tcp_dns_socket_server.py,sha256=UKOgyhCXj-u9KJoP8qsQLbyyIkjOhyG7-_3GSP8e0DM,7908
75
78
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- nopasaran-0.2.95.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
77
- nopasaran-0.2.95.dist-info/METADATA,sha256=tugqx4ZppBaS98cYZASox_JZMjhzKmluIcO1R4P0DUU,5474
78
- nopasaran-0.2.95.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
79
- nopasaran-0.2.95.dist-info/entry_points.txt,sha256=LaOz5GlWuMLjzg4KOEB5OVTattCXVW6a4nSW-WQajCw,55
80
- nopasaran-0.2.95.dist-info/top_level.txt,sha256=60R1FzpprzU8iiJ1cBMNOA0F083_lYoctFo7pzOpMwY,16
81
- nopasaran-0.2.95.dist-info/RECORD,,
79
+ nopasaran-0.2.96.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
80
+ nopasaran-0.2.96.dist-info/METADATA,sha256=sRSoSMacpUl3Yva3uyxmqSaptJ65Wea-m3mrxQ0F638,5496
81
+ nopasaran-0.2.96.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
82
+ nopasaran-0.2.96.dist-info/entry_points.txt,sha256=LaOz5GlWuMLjzg4KOEB5OVTattCXVW6a4nSW-WQajCw,55
83
+ nopasaran-0.2.96.dist-info/top_level.txt,sha256=60R1FzpprzU8iiJ1cBMNOA0F083_lYoctFo7pzOpMwY,16
84
+ nopasaran-0.2.96.dist-info/RECORD,,