rimossi cloudscraper, simplejson e torrentool, aggiornato sambatools

This commit is contained in:
marco
2020-12-26 14:37:12 +01:00
parent 483fab34df
commit e755d71127
147 changed files with 21555 additions and 17142 deletions

View File

@@ -0,0 +1,144 @@
import os, logging, random, socket, time, select
from base import NBNS, NotConnectedError
from nmb_constants import TYPE_CLIENT, TYPE_SERVER, TYPE_WORKSTATION
class NetBIOS(NBNS):
log = logging.getLogger('NMB.NetBIOS')
def __init__(self, broadcast = True, listen_port = 0):
"""
Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
"""
self.broadcast = broadcast
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if self.broadcast:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
if listen_port:
self.sock.bind(( '', listen_port ))
def close(self):
"""
Close the underlying and free resources.
The NetBIOS instance should not be used to perform any operations after this method returns.
:return: None
"""
self.sock.close()
self.sock = None
def write(self, data, ip, port):
assert self.sock, 'Socket is already closed'
self.sock.sendto(data, ( ip, port ))
def queryName(self, name, ip = '', port = 137, timeout = 30):
"""
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
"""
assert self.sock, 'Socket is already closed'
trn_id = random.randint(1, 0xFFFF)
data = self.prepareNameQuery(trn_id, name)
if self.broadcast and not ip:
ip = '<broadcast>'
elif not ip:
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
self.write(data, ip, port)
return self._pollForNetBIOSPacket(trn_id, timeout)
def queryIPForName(self, ip, port = 137, timeout = 30):
"""
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
The implementation of this function is contributed by Jason Anderson.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
"""
assert self.sock, 'Socket is already closed'
trn_id = random.randint(1, 0xFFFF)
data = self.prepareNetNameQuery(trn_id, False)
self.write(data, ip, port)
ret = self._pollForQueryPacket(trn_id, timeout)
if ret:
return map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret))
else:
return None
#
# Protected Methods
#
def _pollForNetBIOSPacket(self, wait_trn_id, timeout):
end_time = time.time() + timeout
while True:
try:
_timeout = end_time - time.time()
if _timeout <= 0:
return None
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
if not ready:
return None
data, _ = self.sock.recvfrom(0xFFFF)
if len(data) == 0:
raise NotConnectedError
trn_id, ret = self.decodePacket(data)
if trn_id == wait_trn_id:
return ret
except select.error, ex:
if type(ex) is types.TupleType:
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex
#
# Contributed by Jason Anderson
#
def _pollForQueryPacket(self, wait_trn_id, timeout):
end_time = time.time() + timeout
while True:
try:
_timeout = end_time - time.time()
if _timeout <= 0:
return None
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
if not ready:
return None
data, _ = self.sock.recvfrom(0xFFFF)
if len(data) == 0:
raise NotConnectedError
trn_id, ret = self.decodeIPQueryPacket(data)
if trn_id == wait_trn_id:
return ret
except select.error, ex:
if type(ex) is types.TupleType:
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex

View File

@@ -0,0 +1,136 @@
import os, logging, random, socket, time
from twisted.internet import reactor, defer
from twisted.internet.protocol import DatagramProtocol
from nmb_constants import TYPE_SERVER
from base import NBNS
IP_QUERY, NAME_QUERY = range(2)
class NetBIOSTimeout(Exception):
"""Raised in NBNSProtocol via Deferred.errback method when queryName method has timeout waiting for reply"""
pass
class NBNSProtocol(DatagramProtocol, NBNS):
log = logging.getLogger('NMB.NBNSProtocol')
def __init__(self, broadcast = True, listen_port = 0):
"""
Instantiate a NBNSProtocol instance.
This automatically calls reactor.listenUDP method to start listening for incoming packets, so you **must not** call the listenUDP method again.
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
"""
self.broadcast = broadcast
self.pending_trns = { } # TRN ID -> ( expiry_time, name, Deferred instance )
self.transport = reactor.listenUDP(listen_port, self)
if self.broadcast:
self.transport.getHandle().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
reactor.callLater(1, self.cleanupPendingTrns)
def datagramReceived(self, data, from_info):
host, port = from_info
trn_id, ret = self.decodePacket(data)
# pending transaction exists for trn_id - handle it and remove from queue
if trn_id in self.pending_trns:
_, ip, d = self.pending_trns.pop(trn_id)
if ip is NAME_QUERY:
# decode as query packet
trn_id, ret = self.decodeIPQueryPacket(data)
d.callback(ret)
def write(self, data, ip, port):
# We don't use the transport.write method directly as it keeps raising DeprecationWarning for ip='<broadcast>'
self.transport.getHandle().sendto(data, ( ip, port ))
def queryName(self, name, ip = '', port = 137, timeout = 30):
"""
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the returned Deferred instance will be called with a NetBIOSTimeout exception.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of IP addresses in dotted notation (aaa.bbb.ccc.ddd).
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
"""
trn_id = random.randint(1, 0xFFFF)
while True:
if not self.pending_trns.has_key(trn_id):
break
else:
trn_id = (trn_id + 1) & 0xFFFF
data = self.prepareNameQuery(trn_id, name)
if self.broadcast and not ip:
ip = '<broadcast>'
elif not ip:
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
self.write(data, ip, port)
d = defer.Deferred()
self.pending_trns[trn_id] = ( time.time()+timeout, name, d )
return d
def queryIPForName(self, ip, port = 137, timeout = 30):
"""
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
The implementation of this function is contributed by Jason Anderson.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of names of the machine at *ip*.
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
"""
trn_id = random.randint(1, 0xFFFF)
while True:
if not self.pending_trns.has_key(trn_id):
break
else:
trn_id = (trn_id + 1) & 0xFFFF
data = self.prepareNetNameQuery(trn_id)
self.write(data, ip, port)
d = defer.Deferred()
d2 = defer.Deferred()
d2.addErrback(d.errback)
def stripCode(ret):
if ret is not None: # got valid response. Somehow the callback is also called when there is an error.
d.callback(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
d2.addCallback(stripCode)
self.pending_trns[trn_id] = ( time.time()+timeout, NAME_QUERY, d2 )
return d
def stopProtocol(self):
DatagramProtocol.stopProtocol(self)
def cleanupPendingTrns(self):
now = time.time()
# reply should have been received in the past
expired = filter(lambda (trn_id, (expiry_time, name, d)): expiry_time < now, self.pending_trns.iteritems())
# remove expired items from dict + call errback
def expire_item(item):
trn_id, (expiry_time, name, d) = item
del self.pending_trns[trn_id]
try:
d.errback(NetBIOSTimeout(name))
except: pass
map(expire_item, expired)
if self.transport:
reactor.callLater(1, self.cleanupPendingTrns)

View File

View File

@@ -0,0 +1,185 @@
import struct, logging, random
from nmb_constants import *
from nmb_structs import *
from utils import encode_name
class NMBSession:
log = logging.getLogger('NMB.NMBSession')
def __init__(self, my_name, remote_name, host_type = TYPE_SERVER, is_direct_tcp = False):
self.my_name = my_name.upper()
self.remote_name = remote_name.upper()
self.host_type = host_type
self.data_buf = ''
if is_direct_tcp:
self.data_nmb = DirectTCPSessionMessage()
self.sendNMBPacket = self._sendNMBPacket_DirectTCP
else:
self.data_nmb = NMBSessionMessage()
self.sendNMBPacket = self._sendNMBPacket_NetBIOS
#
# Overridden Methods
#
def write(self, data):
raise NotImplementedError
def onNMBSessionMessage(self, flags, data):
pass
def onNMBSessionOK(self):
pass
def onNMBSessionFailed(self):
pass
#
# Public Methods
#
def feedData(self, data):
self.data_buf = self.data_buf + data
offset = 0
while True:
length = self.data_nmb.decode(self.data_buf, offset)
if length == 0:
break
elif length > 0:
offset += length
self._processNMBSessionPacket(self.data_nmb)
else:
raise NMBError
if offset > 0:
self.data_buf = self.data_buf[offset:]
def sendNMBMessage(self, data):
self.sendNMBPacket(SESSION_MESSAGE, data)
def requestNMBSession(self):
my_name_encoded = encode_name(self.my_name, TYPE_WORKSTATION)
remote_name_encoded = encode_name(self.remote_name, self.host_type)
self.sendNMBPacket(SESSION_REQUEST, remote_name_encoded + my_name_encoded)
#
# Protected Methods
#
def _processNMBSessionPacket(self, packet):
if packet.type == SESSION_MESSAGE:
self.onNMBSessionMessage(packet.flags, packet.data)
elif packet.type == POSITIVE_SESSION_RESPONSE:
self.onNMBSessionOK()
elif packet.type == NEGATIVE_SESSION_RESPONSE:
self.onNMBSessionFailed()
elif packet.type == SESSION_KEEPALIVE:
# Discard keepalive packets - [RFC1002]: 5.2.2.1
pass
else:
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
def _sendNMBPacket_NetBIOS(self, packet_type, data):
length = len(data)
assert length <= 0x01FFFF
flags = 0
if length > 0xFFFF:
flags |= 0x01
length &= 0xFFFF
self.write(struct.pack('>BBH', packet_type, flags, length) + data)
def _sendNMBPacket_DirectTCP(self, packet_type, data):
length = len(data)
assert length <= 0x00FFFFFF
self.write(struct.pack('>I', length) + data)
class NBNS:
log = logging.getLogger('NMB.NBNS')
HEADER_STRUCT_FORMAT = '>HHHHHH'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def write(self, data, ip, port):
raise NotImplementedError
def decodePacket(self, data):
if len(data) < self.HEADER_STRUCT_SIZE:
raise Exception
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
is_response = bool((code >> 15) & 0x01)
opcode = (code >> 11) & 0x0F
flags = (code >> 4) & 0x7F
rcode = code & 0x0F
if opcode == 0x0000 and is_response:
name_len = ord(data[self.HEADER_STRUCT_SIZE])
offset = self.HEADER_STRUCT_SIZE+2+name_len+8 # constant 2 for the padding bytes before/after the Name and constant 8 for the Type, Class and TTL fields in the Answer section after the Name
record_count = (struct.unpack('>H', data[offset:offset+2])[0]) / 6
offset += 4 # Constant 4 for the Data Length and Flags field
ret = [ ]
for i in range(0, record_count):
ret.append('%d.%d.%d.%d' % struct.unpack('4B', (data[offset:offset + 4])))
offset += 6
return trn_id, ret
else:
return trn_id, None
def prepareNameQuery(self, trn_id, name, is_broadcast = True):
header = struct.pack(self.HEADER_STRUCT_FORMAT,
trn_id, (is_broadcast and 0x0110) or 0x0100, 1, 0, 0, 0)
payload = encode_name(name, 0x20) + '\x00\x20\x00\x01'
return header + payload
#
# Contributed by Jason Anderson
#
def decodeIPQueryPacket(self, data):
if len(data) < self.HEADER_STRUCT_SIZE:
raise Exception
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
is_response = bool((code >> 15) & 0x01)
opcode = (code >> 11) & 0x0F
flags = (code >> 4) & 0x7F
rcode = code & 0x0F
try:
numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0]
if numnames > 0:
ret = [ ]
offset = self.HEADER_STRUCT_SIZE + 45
for i in range(0, numnames):
mynme = data[offset:offset + 15]
mynme = mynme.strip()
ret.append(( mynme, ord(data[offset+15]) ))
offset += 18
return trn_id, ret
except IndexError:
pass
return trn_id, None
#
# Contributed by Jason Anderson
#
def prepareNetNameQuery(self, trn_id, is_broadcast = True):
header = struct.pack(self.HEADER_STRUCT_FORMAT,
trn_id, (is_broadcast and 0x0010) or 0x0000, 1, 0, 0, 0)
payload = encode_name('*', 0) + '\x00\x21\x00\x01'
return header + payload

View File

@@ -0,0 +1,38 @@
# Default port for NetBIOS name service
NETBIOS_NS_PORT = 137
# Default port for NetBIOS session service
NETBIOS_SESSION_PORT = 139
# Owner Node Type Constants
NODE_B = 0x00
NODE_P = 0x01
NODE_M = 0x10
NODE_RESERVED = 0x11
# Name Type Constants
TYPE_UNKNOWN = 0x01
TYPE_WORKSTATION = 0x00
TYPE_CLIENT = 0x03
TYPE_SERVER = 0x20
TYPE_DOMAIN_MASTER = 0x1B
TYPE_MASTER_BROWSER = 0x1D
TYPE_BROWSER = 0x1E
TYPE_NAMES = { TYPE_UNKNOWN: 'Unknown',
TYPE_WORKSTATION: 'Workstation',
TYPE_CLIENT: 'Client',
TYPE_SERVER: 'Server',
TYPE_MASTER_BROWSER: 'Master Browser',
TYPE_BROWSER: 'Browser Server',
TYPE_DOMAIN_MASTER: 'Domain Master'
}
# Values for Session Packet Type field in Session Packets
SESSION_MESSAGE = 0x00
SESSION_REQUEST = 0x81
POSITIVE_SESSION_RESPONSE = 0x82
NEGATIVE_SESSION_RESPONSE = 0x83
REGTARGET_SESSION_RESPONSE = 0x84
SESSION_KEEPALIVE = 0x85

View File

@@ -0,0 +1,69 @@
import struct
class NMBError(Exception): pass
class NotConnectedError(NMBError):
"""
Raisd when the underlying NMB connection has been disconnected or not connected yet
"""
pass
class NMBSessionMessage:
HEADER_STRUCT_FORMAT = '>BBH'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def __init__(self):
self.reset()
def reset(self):
self.type = 0
self.flags = 0
self.data = ''
def decode(self, data, offset):
data_len = len(data)
if data_len < offset + self.HEADER_STRUCT_SIZE:
# Not enough data for decoding
return 0
self.reset()
self.type, self.flags, length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])
if self.flags & 0x01:
length |= 0x010000
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
return 0
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
return self.HEADER_STRUCT_SIZE + length
class DirectTCPSessionMessage(NMBSessionMessage):
HEADER_STRUCT_FORMAT = '>I'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def decode(self, data, offset):
data_len = len(data)
if data_len < offset + self.HEADER_STRUCT_SIZE:
# Not enough data for decoding
return 0
self.reset()
length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])[0]
if length >> 24 != 0:
raise NMBError("Invalid protocol header for Direct TCP session message")
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
return 0
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
return self.HEADER_STRUCT_SIZE + length

View File

@@ -0,0 +1,50 @@
import string, re
def encode_name(name, type, scope = None):
"""
Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
"""
if name == '*':
name = name + '\0' * 15
elif len(name) > 15:
name = name[:15] + chr(type)
else:
name = string.ljust(name, 15) + chr(type)
def _do_first_level_encoding(m):
s = ord(m.group(0))
return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
if scope:
encoded_scope = ''
for s in string.split(scope, '.'):
encoded_scope = encoded_scope + chr(len(s)) + s
return encoded_name + encoded_scope + '\0'
else:
return encoded_name + '\0'
def decode_name(name):
name_length = ord(name[0])
assert name_length == 32
def _do_first_level_decoding(m):
s = m.group(0)
return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
if name[33] == '\0':
return 34, decoded_name, ''
else:
decoded_domain = ''
offset = 34
while 1:
domain_length = ord(name[offset])
if domain_length == 0:
break
decoded_domain = '.' + name[offset:offset + domain_length]
offset = offset + domain_length
return offset + 1, decoded_name, decoded_domain