rimossi cloudscraper, simplejson e torrentool, aggiornato sambatools
This commit is contained in:
144
lib/sambatools/python3/nmb/NetBIOS.py
Normal file
144
lib/sambatools/python3/nmb/NetBIOS.py
Normal 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 list(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 = time.time()-end_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 as ex:
|
||||
if type(ex) is tuple:
|
||||
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 = time.time()-end_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 as ex:
|
||||
if type(ex) is tuple:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
134
lib/sambatools/python3/nmb/NetBIOSProtocol.py
Normal file
134
lib/sambatools/python3/nmb/NetBIOSProtocol.py
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
import os, logging, random, socket, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
from .base import NBNS
|
||||
|
||||
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 trn_id not in self.pending_trns:
|
||||
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 trn_id not in self.pending_trns:
|
||||
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
|
||||
# info is tuple of ( expiry_time, name, d )
|
||||
expired = filter(lambda trn_id, info: info[0] < 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)
|
||||
0
lib/sambatools/python3/nmb/__init__.py
Normal file
0
lib/sambatools/python3/nmb/__init__.py
Normal file
187
lib/sambatools/python3/nmb/base.py
Normal file
187
lib/sambatools/python3/nmb/base.py
Normal file
@@ -0,0 +1,187 @@
|
||||
|
||||
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 = b''
|
||||
|
||||
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 = data[self.HEADER_STRUCT_SIZE]
|
||||
# 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:
|
||||
offset = self.HEADER_STRUCT_SIZE + 2 + name_len + 8
|
||||
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) + b'\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 = data[self.HEADER_STRUCT_SIZE + 44]
|
||||
|
||||
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(( str(mynme, 'ascii'), 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) + b'\x00\x21\x00\x01'
|
||||
|
||||
return header + payload
|
||||
38
lib/sambatools/python3/nmb/nmb_constants.py
Normal file
38
lib/sambatools/python3/nmb/nmb_constants.py
Normal 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
|
||||
66
lib/sambatools/python3/nmb/nmb_structs.py
Normal file
66
lib/sambatools/python3/nmb/nmb_structs.py
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
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
|
||||
50
lib/sambatools/python3/nmb/utils.py
Normal file
50
lib/sambatools/python3/nmb/utils.py
Normal 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 = name.ljust(15) + chr(type)
|
||||
|
||||
def _do_first_level_encoding(m):
|
||||
s = ord(m.group(0))
|
||||
return string.ascii_uppercase[s >> 4] + string.ascii_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 bytes(encoded_name + encoded_scope + '\0', 'ascii')
|
||||
else:
|
||||
return bytes(encoded_name + '\0', 'ascii')
|
||||
|
||||
|
||||
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
|
||||
637
lib/sambatools/python3/smb/SMBConnection.py
Normal file
637
lib/sambatools/python3/smb/SMBConnection.py
Normal file
@@ -0,0 +1,637 @@
|
||||
|
||||
import os, logging, select, socket, types, struct, errno
|
||||
from .smb_constants import *
|
||||
from .smb_structs import *
|
||||
from .base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
|
||||
|
||||
class SMBConnection(SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBConnection')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBConnection instance.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
*password* can be a string or a callable returning a string.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication.
|
||||
|
||||
The default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
SMB.__init__(self, username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp)
|
||||
self.sock = None
|
||||
self.auth_result = None
|
||||
self.is_busy = False
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
self.auth_result = True
|
||||
|
||||
def onAuthFailed(self):
|
||||
self.auth_result = False
|
||||
|
||||
def write(self, data):
|
||||
assert self.sock
|
||||
data_len = len(data)
|
||||
total_sent = 0
|
||||
while total_sent < data_len:
|
||||
sent = self.sock.send(data[total_sent:])
|
||||
if sent == 0:
|
||||
raise NotConnectedError('Server disconnected')
|
||||
total_sent = total_sent + sent
|
||||
|
||||
#
|
||||
# Support for "with" context
|
||||
#
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
#
|
||||
# Misc Properties
|
||||
#
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.is_using_smb2
|
||||
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def connect(self, ip, port = 139, sock_family = socket.AF_INET, timeout = 60):
|
||||
"""
|
||||
Establish the SMB connection to the remote SMB/CIFS server.
|
||||
|
||||
You must call this method before attempting any of the file operations with the remote server.
|
||||
This method will block until the SMB connection has attempted at least one authentication.
|
||||
|
||||
:return: A boolean value indicating the result of the authentication atttempt: True if authentication is successful; False, if otherwise.
|
||||
"""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
|
||||
self.auth_result = None
|
||||
self.sock = socket.socket(sock_family)
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.connect(( ip, port ))
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
while self.auth_result is None:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return self.auth_result
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Terminate the SMB connection (if it has been started) and release any sources held by the underlying socket.
|
||||
"""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:return: A list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances describing the shared resource
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listShares(cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||
|
||||
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listPath(service_name, path, cb, eb, search = search, pattern = pattern, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A list of python *datetime.DateTime* instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listSnapshots(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(info):
|
||||
self.is_busy = False
|
||||
results.append(info)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._getAttributes(service_name, path, cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def getSecurity(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve the security descriptor of the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(info):
|
||||
self.is_busy = False
|
||||
results.append(info)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._getSecurity(service_name, path, cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you wish to specify the offset to read from the remote *path* and/or the number of bytes to write to the *file_obj*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
|
||||
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0, -1, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0, max_length = -1, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* up to *max_length* number of bytes. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the method returns immediately after the file is opened successfully for reading.
|
||||
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r[1:])
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._retrieveFileFromOffset(service_name, path, file_obj, cb, eb, offset, max_length, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
If the file already exists on the remote server, it will be truncated and overwritten.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. In Python3, this file-like object must have a *read* method which returns a bytes parameter.
|
||||
:return: Number of bytes uploaded
|
||||
"""
|
||||
return self.storeFileFromOffset(service_name, path, file_obj, 0, True, timeout)
|
||||
|
||||
def storeFileFromOffset(self, service_name, path, file_obj, offset = 0, truncate = False, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:param offset: Long integer value which specifies the offset in the remote server to start writing. First byte of the file is 0.
|
||||
:param truncate: Boolean value. If True and the file exists on the remote server, it will be truncated first before writing. Default is False.
|
||||
:return: the file position where the next byte will be written.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r[1])
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._storeFileFromOffset(service_name, path, file_obj, cb, eb, offset, truncate = truncate, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def resetFileAttributes(self, service_name, path_file_pattern, file_attributes = ATTR_NORMAL, timeout = 30):
|
||||
"""
|
||||
Reset file attributes of one or more regular files or folders.
|
||||
It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request.
|
||||
This function is very helpful when deleting files/folders that are read-only.
|
||||
By default, it sets the ATTR_NORMAL flag, therefore clearing all other flags.
|
||||
(See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information)
|
||||
|
||||
Note: this function is currently only implemented for SMB2!
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in the filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:param int file_attributes: The desired file attributes to set. Defaults to `ATTR_NORMAL`.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._resetFileAttributes(service_name, path_file_pattern, cb, eb, file_attributes, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def createDirectory(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._createDirectory(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def deleteDirectory(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._deleteDirectory(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def rename(self, service_name, old_path, new_path, timeout = 30):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._rename(service_name, old_path, new_path, cb, eb)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||
:return: The *data* parameter
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._echo(data, cb, eb)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _pollForNetBIOSPacket(self, timeout):
|
||||
expiry_time = time.time() + timeout
|
||||
read_len = 4
|
||||
data = b''
|
||||
|
||||
while read_len > 0:
|
||||
try:
|
||||
if expiry_time < time.time():
|
||||
raise SMBTimeout
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
|
||||
if not ready:
|
||||
raise SMBTimeout
|
||||
|
||||
d = self.sock.recv(read_len)
|
||||
if len(d) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
data = data + d
|
||||
read_len -= len(d)
|
||||
except select.error as ex:
|
||||
if isinstance(ex, tuple):
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
type, flags, length = struct.unpack('>BBH', data)
|
||||
if flags & 0x01:
|
||||
length = length | 0x10000
|
||||
|
||||
read_len = length
|
||||
while read_len > 0:
|
||||
try:
|
||||
if expiry_time < time.time():
|
||||
raise SMBTimeout
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
|
||||
if not ready:
|
||||
raise SMBTimeout
|
||||
|
||||
d = self.sock.recv(read_len)
|
||||
if len(d) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
data = data + d
|
||||
read_len -= len(d)
|
||||
except select.error as ex:
|
||||
if isinstance(ex, tuple):
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
self.feedData(data)
|
||||
96
lib/sambatools/python3/smb/SMBHandler.py
Normal file
96
lib/sambatools/python3/smb/SMBHandler.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import os, sys, socket, urllib.request, urllib.error, urllib.parse, mimetypes, email, tempfile
|
||||
from urllib.parse import (unwrap, unquote, splittype, splithost, quote,
|
||||
splitport, splittag, splitattr, splituser, splitpasswd, splitvalue)
|
||||
from urllib.response import addinfourl
|
||||
from urllib.request import ftpwrapper
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
USE_NTLM = True
|
||||
MACHINE_NAME = None
|
||||
|
||||
class SMBHandler(urllib.request.BaseHandler):
|
||||
|
||||
def smb_open(self, req):
|
||||
global USE_NTLM, MACHINE_NAME
|
||||
|
||||
if not req.host:
|
||||
raise urllib.error.URLError('SMB error: no host given')
|
||||
host, port = splitport(req.host)
|
||||
if port is None:
|
||||
port = 139
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
# username/password handling
|
||||
|
||||
user, host = splituser(host)
|
||||
|
||||
if user:
|
||||
user, passwd = splitpasswd(user)
|
||||
else:
|
||||
passwd = None
|
||||
|
||||
host = unquote(host)
|
||||
user = user or ''
|
||||
|
||||
domain = ''
|
||||
if ';' in user:
|
||||
domain, user = user.split(';', 1)
|
||||
|
||||
passwd = passwd or ''
|
||||
myname = MACHINE_NAME or self.generateClientMachineName()
|
||||
|
||||
server_name,host = host.split(',') if ',' in host else [None,host]
|
||||
|
||||
if server_name is None:
|
||||
n = NetBIOS()
|
||||
|
||||
names = n.queryIPForName(host)
|
||||
if names:
|
||||
server_name = names[0]
|
||||
else:
|
||||
raise urllib.error.URLError('SMB error: Hostname does not reply back with its machine name')
|
||||
|
||||
path, attrs = splitattr(req.selector)
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
dirs = path.split('/')
|
||||
dirs = list(map(unquote, dirs))
|
||||
service, path = dirs[0], '/'.join(dirs[1:])
|
||||
|
||||
try:
|
||||
conn = SMBConnection(user, passwd, myname, server_name, domain=domain, use_ntlm_v2 = USE_NTLM)
|
||||
conn.connect(host, port)
|
||||
|
||||
headers = email.message.Message()
|
||||
if req.data:
|
||||
filelen = conn.storeFile(service, path, req.data)
|
||||
|
||||
headers.add_header('Content-length', '0')
|
||||
fp = BytesIO(b"")
|
||||
else:
|
||||
fp = self.createTempFile()
|
||||
file_attrs, retrlen = conn.retrieveFile(service, path, fp)
|
||||
fp.seek(0)
|
||||
|
||||
mtype = mimetypes.guess_type(req.get_full_url())[0]
|
||||
if mtype:
|
||||
headers.add_header('Content-type', mtype)
|
||||
if retrlen is not None and retrlen >= 0:
|
||||
headers.add_header('Content-length', '%d' % retrlen)
|
||||
|
||||
return addinfourl(fp, headers, req.get_full_url())
|
||||
except Exception as ex:
|
||||
raise urllib.error.URLError('smb error: %s' % ex).with_traceback(sys.exc_info()[2])
|
||||
|
||||
def createTempFile(self):
|
||||
return tempfile.TemporaryFile()
|
||||
|
||||
def generateClientMachineName(self):
|
||||
hostname = socket.gethostname()
|
||||
if hostname:
|
||||
return hostname.split('.')[0]
|
||||
return 'SMB%d' % os.getpid()
|
||||
412
lib/sambatools/python3/smb/SMBProtocol.py
Normal file
412
lib/sambatools/python3/smb/SMBProtocol.py
Normal file
@@ -0,0 +1,412 @@
|
||||
|
||||
import os, logging, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import ClientFactory, Protocol
|
||||
from .smb_constants import *
|
||||
from .smb_structs import *
|
||||
from .base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
|
||||
|
||||
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
||||
|
||||
|
||||
class SMBProtocol(Protocol, SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBProtocol')
|
||||
|
||||
#
|
||||
# Protocol Methods
|
||||
#
|
||||
|
||||
def connectionMade(self):
|
||||
self.factory.instance = self
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.factory.instance == self:
|
||||
self.instance = None
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.feedData(data)
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
self.transport.write(data)
|
||||
|
||||
def onAuthOK(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthOK()
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
def onAuthFailed(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthFailed()
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _cleanupPendingRequests(self):
|
||||
if self.factory.instance == self:
|
||||
now = time.time()
|
||||
to_remove = []
|
||||
for mid, r in self.pending_requests.items():
|
||||
if r.expiry_time < now:
|
||||
try:
|
||||
r.errback(SMBTimeout())
|
||||
except Exception: pass
|
||||
to_remove.append(mid)
|
||||
|
||||
for mid in to_remove:
|
||||
del self.pending_requests[mid]
|
||||
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
|
||||
class SMBProtocolFactory(ClientFactory):
|
||||
|
||||
protocol = SMBProtocol
|
||||
log = logging.getLogger('SMB.SMBFactory')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server.
|
||||
Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.my_name = my_name
|
||||
self.remote_name = remote_name
|
||||
self.domain = domain
|
||||
self.use_ntlm_v2 = use_ntlm_v2
|
||||
self.sign_options = sign_options
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
|
||||
|
||||
#
|
||||
# Public Property
|
||||
#
|
||||
|
||||
@property
|
||||
def isReady(self):
|
||||
"""A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations."""
|
||||
return bool(self.instance and self.instance.has_authenticated)
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.instance and self.instance.is_using_smb2
|
||||
|
||||
#
|
||||
# Public Methods for Callbacks
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
File operations can proceed when this method has been called.
|
||||
"""
|
||||
pass
|
||||
|
||||
def onAuthFailed(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
|
||||
If you want to retry authenticating from this method,
|
||||
1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``)
|
||||
2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag.
|
||||
3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection
|
||||
"""
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listShares(d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||
|
||||
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
|
||||
return d
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime*
|
||||
instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0, -1, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0, max_length = -1, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
|
||||
return d
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged
|
||||
by the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. In Python3, this file-like object must have a *read* method which returns a bytes parameter.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteFiles(service_name, path_file_pattern, delete_matching_folders, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def createDirectory(self, service_name, path):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._createDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def deleteDirectory(self, service_name, path):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def rename(self, service_name, old_path, new_path):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._echo(data, d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def closeConnection(self):
|
||||
"""
|
||||
Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
self.instance.transport.loseConnection()
|
||||
|
||||
#
|
||||
# ClientFactory methods
|
||||
# (Do not touch these unless you know what you are doing)
|
||||
#
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp)
|
||||
p.factory = self
|
||||
return p
|
||||
1
lib/sambatools/python3/smb/__init__.py
Normal file
1
lib/sambatools/python3/smb/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
3040
lib/sambatools/python3/smb/base.py
Normal file
3040
lib/sambatools/python3/smb/base.py
Normal file
File diff suppressed because it is too large
Load Diff
254
lib/sambatools/python3/smb/ntlm.py
Normal file
254
lib/sambatools/python3/smb/ntlm.py
Normal file
@@ -0,0 +1,254 @@
|
||||
|
||||
import types, hmac, binascii, struct, random, string
|
||||
from .utils.rc4 import RC4_encrypt
|
||||
from .utils.pyDes import des
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
hashlib.new('md4')
|
||||
|
||||
def MD4(): return hashlib.new('md4')
|
||||
except ( ImportError, ValueError ):
|
||||
from .utils.md4 import MD4
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
def MD5(s): return hashlib.md5(s)
|
||||
except ImportError:
|
||||
import md5
|
||||
def MD5(s): return md5.new(s)
|
||||
|
||||
################
|
||||
# NTLMv2 Methods
|
||||
################
|
||||
|
||||
# The following constants are defined in accordance to [MS-NLMP]: 2.2.2.5
|
||||
|
||||
NTLM_NegotiateUnicode = 0x00000001
|
||||
NTLM_NegotiateOEM = 0x00000002
|
||||
NTLM_RequestTarget = 0x00000004
|
||||
NTLM_Unknown9 = 0x00000008
|
||||
NTLM_NegotiateSign = 0x00000010
|
||||
NTLM_NegotiateSeal = 0x00000020
|
||||
NTLM_NegotiateDatagram = 0x00000040
|
||||
NTLM_NegotiateLanManagerKey = 0x00000080
|
||||
NTLM_Unknown8 = 0x00000100
|
||||
NTLM_NegotiateNTLM = 0x00000200
|
||||
NTLM_NegotiateNTOnly = 0x00000400
|
||||
NTLM_Anonymous = 0x00000800
|
||||
NTLM_NegotiateOemDomainSupplied = 0x00001000
|
||||
NTLM_NegotiateOemWorkstationSupplied = 0x00002000
|
||||
NTLM_Unknown6 = 0x00004000
|
||||
NTLM_NegotiateAlwaysSign = 0x00008000
|
||||
NTLM_TargetTypeDomain = 0x00010000
|
||||
NTLM_TargetTypeServer = 0x00020000
|
||||
NTLM_TargetTypeShare = 0x00040000
|
||||
NTLM_NegotiateExtendedSecurity = 0x00080000
|
||||
NTLM_NegotiateIdentify = 0x00100000
|
||||
NTLM_Unknown5 = 0x00200000
|
||||
NTLM_RequestNonNTSessionKey = 0x00400000
|
||||
NTLM_NegotiateTargetInfo = 0x00800000
|
||||
NTLM_Unknown4 = 0x01000000
|
||||
NTLM_NegotiateVersion = 0x02000000
|
||||
NTLM_Unknown3 = 0x04000000
|
||||
NTLM_Unknown2 = 0x08000000
|
||||
NTLM_Unknown1 = 0x10000000
|
||||
NTLM_Negotiate128 = 0x20000000
|
||||
NTLM_NegotiateKeyExchange = 0x40000000
|
||||
NTLM_Negotiate56 = 0x80000000
|
||||
|
||||
NTLM_FLAGS = NTLM_NegotiateUnicode | \
|
||||
NTLM_RequestTarget | \
|
||||
NTLM_NegotiateSign | \
|
||||
NTLM_NegotiateNTLM | \
|
||||
NTLM_NegotiateAlwaysSign | \
|
||||
NTLM_NegotiateExtendedSecurity | \
|
||||
NTLM_NegotiateTargetInfo | \
|
||||
NTLM_NegotiateVersion | \
|
||||
NTLM_Negotiate128 | \
|
||||
NTLM_NegotiateKeyExchange
|
||||
|
||||
def generateNegotiateMessage():
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.1
|
||||
"""
|
||||
s = struct.pack('<8sII8s8s8s',
|
||||
b'NTLMSSP\0', 0x01, NTLM_FLAGS,
|
||||
b'\0' * 8, # Domain
|
||||
b'\0' * 8, # Workstation
|
||||
b'\x06\x00\x72\x17\x00\x00\x00\x0F') # Version [MS-NLMP]: 2.2.2.10
|
||||
return s
|
||||
|
||||
|
||||
def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, request_session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.3
|
||||
"""
|
||||
FORMAT = '<8sIHHIHHIHHIHHIHHIHHII'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
# [MS-NLMP]: 3.1.5.1.2
|
||||
# http://grutz.jingojango.net/exploits/davenport-ntlm.html
|
||||
session_key = session_signing_key = request_session_key
|
||||
if challenge_flags & NTLM_NegotiateKeyExchange:
|
||||
session_signing_key = "".join([ random.choice(string.digits+string.ascii_letters) for _ in range(16) ]).encode('ascii')
|
||||
session_key = RC4_encrypt(request_session_key, session_signing_key)
|
||||
|
||||
lm_response_length = len(lm_response)
|
||||
lm_response_offset = FORMAT_SIZE
|
||||
nt_response_length = len(nt_response)
|
||||
nt_response_offset = lm_response_offset + lm_response_length
|
||||
domain_unicode = domain.encode('UTF-16LE')
|
||||
domain_length = len(domain_unicode)
|
||||
domain_offset = nt_response_offset + nt_response_length
|
||||
|
||||
padding = b''
|
||||
if domain_offset % 2 != 0:
|
||||
padding = b'\0'
|
||||
domain_offset += 1
|
||||
|
||||
user_unicode = user.encode('UTF-16LE')
|
||||
user_length = len(user_unicode)
|
||||
user_offset = domain_offset + domain_length
|
||||
workstation_unicode = workstation.encode('UTF-16LE')
|
||||
workstation_length = len(workstation_unicode)
|
||||
workstation_offset = user_offset + user_length
|
||||
session_key_length = len(session_key)
|
||||
session_key_offset = workstation_offset + workstation_length
|
||||
|
||||
auth_flags = challenge_flags
|
||||
auth_flags &= ~NTLM_NegotiateVersion
|
||||
|
||||
s = struct.pack(FORMAT,
|
||||
b'NTLMSSP\0', 0x03,
|
||||
lm_response_length, lm_response_length, lm_response_offset,
|
||||
nt_response_length, nt_response_length, nt_response_offset,
|
||||
domain_length, domain_length, domain_offset,
|
||||
user_length, user_length, user_offset,
|
||||
workstation_length, workstation_length, workstation_offset,
|
||||
session_key_length, session_key_length, session_key_offset,
|
||||
auth_flags)
|
||||
|
||||
return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key, session_signing_key
|
||||
|
||||
|
||||
def decodeChallengeMessage(ntlm_data):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.2
|
||||
- [MS-NLMP]: 2.2.2.1 (AV_PAIR)
|
||||
"""
|
||||
FORMAT = '<8sIHHII8s8sHHI'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
signature, message_type, \
|
||||
targetname_len, targetname_maxlen, targetname_offset, \
|
||||
flags, challenge, _, \
|
||||
targetinfo_len, targetinfo_maxlen, targetinfo_offset, \
|
||||
= struct.unpack(FORMAT, bytes(ntlm_data[:FORMAT_SIZE]))
|
||||
|
||||
assert signature == b'NTLMSSP\0'
|
||||
assert message_type == 0x02
|
||||
|
||||
return challenge, flags, bytes(ntlm_data[targetinfo_offset:targetinfo_offset+targetinfo_len])
|
||||
|
||||
|
||||
def generateChallengeResponseV2(password, user, server_challenge, server_info, domain = '', client_challenge = None):
|
||||
client_timestamp = b'\0' * 8
|
||||
|
||||
if not client_challenge:
|
||||
client_challenge = bytes([ random.getrandbits(8) for i in range(0, 8) ])
|
||||
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
ntlm_hash = d.digest() # The NT password hash
|
||||
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE'), 'md5').digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
||||
temp = b'\x01\x01' + b'\0'*6 + client_timestamp + client_challenge + b'\0'*4 + server_info
|
||||
ntproofstr = hmac.new(response_key, server_challenge + temp, 'md5').digest()
|
||||
|
||||
nt_challenge_response = ntproofstr + temp
|
||||
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge, 'md5').digest() + client_challenge
|
||||
session_key = hmac.new(response_key, ntproofstr, 'md5').digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
|
||||
################
|
||||
# NTLMv1 Methods
|
||||
################
|
||||
|
||||
def expandDesKey(key):
|
||||
"""Expand the key from a 7-byte password key into a 8-byte DES key"""
|
||||
s = [ ((key[0] >> 1) & 0x7f) << 1,
|
||||
((key[0] & 0x01) << 6 | ((key[1] >> 2) & 0x3f)) << 1,
|
||||
((key[1] & 0x03) << 5 | ((key[2] >> 3) & 0x1f)) << 1,
|
||||
((key[2] & 0x07) << 4 | ((key[3] >> 4) & 0x0f)) << 1,
|
||||
((key[3] & 0x0f) << 3 | ((key[4] >> 5) & 0x07)) << 1,
|
||||
((key[4] & 0x1f) << 2 | ((key[5] >> 6) & 0x03)) << 1,
|
||||
((key[5] & 0x3f) << 1 | ((key[6] >> 7) & 0x01)) << 1,
|
||||
(key[6] & 0x7f) << 1
|
||||
]
|
||||
return bytes(s)
|
||||
|
||||
|
||||
def DESL(K, D):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.4)
|
||||
- [MS-NLMP]: Section 6
|
||||
"""
|
||||
d1 = des(expandDesKey(K[0:7]))
|
||||
d2 = des(expandDesKey(K[7:14]))
|
||||
d3 = des(expandDesKey(K[14:16] + b'\0' * 5))
|
||||
return d1.encrypt(D) + d2.encrypt(D) + d3.encrypt(D)
|
||||
|
||||
|
||||
def generateChallengeResponseV1(password, server_challenge, has_extended_security = False, client_challenge = None):
|
||||
"""
|
||||
Generate a NTLMv1 response
|
||||
|
||||
@param password: User password string
|
||||
@param server_challange: A 8-byte challenge string sent from the server
|
||||
@param has_extended_security: A boolean value indicating whether NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is enabled in the NTLM negFlag
|
||||
@param client_challenge: A 8-byte string representing client challenge. If None, it will be generated randomly if needed by the response generation
|
||||
@return: a tuple of ( NT challenge response string, LM challenge response string )
|
||||
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.3 and 2.8.3.4)
|
||||
- [MS-NLMP]: 3.3.1
|
||||
"""
|
||||
_password = bytes((password.upper() + '\0' * 14)[:14], 'ascii')
|
||||
d1 = des(expandDesKey(_password[:7]))
|
||||
d2 = des(expandDesKey(_password[7:]))
|
||||
lm_response_key = d1.encrypt(b"KGS!@#$%") + d2.encrypt(b"KGS!@#$%") # LM password hash. In [MS-NLMP], this is the result of LMOWFv1 function
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
nt_response_key = d.digest() # In [MS-NLMP], this is the result of NTOWFv1 function
|
||||
|
||||
if has_extended_security:
|
||||
if not client_challenge:
|
||||
client_challenge = bytes([ random.getrandbits(8) for i in range(0, 8) ])
|
||||
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
lm_challenge_response = client_challenge + b'\0'*16
|
||||
nt_challenge_response = DESL(nt_response_key, MD5(server_challenge + client_challenge).digest()[0:8])
|
||||
else:
|
||||
nt_challenge_response = DESL(nt_response_key, server_challenge) # The result after DESL is the NT response
|
||||
lm_challenge_response = DESL(lm_response_key, server_challenge) # The result after DESL is the LM response
|
||||
|
||||
d = MD4()
|
||||
d.update(nt_response_key)
|
||||
session_key = d.digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
367
lib/sambatools/python3/smb/security_descriptors.py
Normal file
367
lib/sambatools/python3/smb/security_descriptors.py
Normal file
@@ -0,0 +1,367 @@
|
||||
"""
|
||||
This module implements security descriptors, and the partial structures
|
||||
used in them, as specified in [MS-DTYP].
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
# Security descriptor control flags
|
||||
# [MS-DTYP]: 2.4.6
|
||||
SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
|
||||
SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
|
||||
SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
|
||||
SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
|
||||
SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
|
||||
SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
|
||||
SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
|
||||
SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
|
||||
SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
|
||||
SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
|
||||
SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
|
||||
SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
|
||||
SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
|
||||
SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
|
||||
SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
|
||||
SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
|
||||
|
||||
# ACE types
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_TYPE_ACCESS_ALLOWED = 0x00
|
||||
ACE_TYPE_ACCESS_DENIED = 0x01
|
||||
ACE_TYPE_SYSTEM_AUDIT = 0x02
|
||||
ACE_TYPE_SYSTEM_ALARM = 0x03
|
||||
ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
|
||||
ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
|
||||
ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
|
||||
ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
|
||||
ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
|
||||
ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
|
||||
|
||||
# ACE flags
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_FLAG_OBJECT_INHERIT = 0x01
|
||||
ACE_FLAG_CONTAINER_INHERIT = 0x02
|
||||
ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
|
||||
ACE_FLAG_INHERIT_ONLY = 0x08
|
||||
ACE_FLAG_INHERITED = 0x10
|
||||
ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
|
||||
ACE_FLAG_FAILED_ACCESS = 0x80
|
||||
|
||||
# Pre-defined well-known SIDs
|
||||
# [MS-DTYP]: 2.4.2.4
|
||||
SID_NULL = "S-1-0-0"
|
||||
SID_EVERYONE = "S-1-1-0"
|
||||
SID_LOCAL = "S-1-2-0"
|
||||
SID_CONSOLE_LOGON = "S-1-2-1"
|
||||
SID_CREATOR_OWNER = "S-1-3-0"
|
||||
SID_CREATOR_GROUP = "S-1-3-1"
|
||||
SID_OWNER_SERVER = "S-1-3-2"
|
||||
SID_GROUP_SERVER = "S-1-3-3"
|
||||
SID_OWNER_RIGHTS = "S-1-3-4"
|
||||
SID_NT_AUTHORITY = "S-1-5"
|
||||
SID_DIALUP = "S-1-5-1"
|
||||
SID_NETWORK = "S-1-5-2"
|
||||
SID_BATCH = "S-1-5-3"
|
||||
SID_INTERACTIVE = "S-1-5-4"
|
||||
SID_SERVICE = "S-1-5-6"
|
||||
SID_ANONYMOUS = "S-1-5-7"
|
||||
SID_PROXY = "S-1-5-8"
|
||||
SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
|
||||
SID_PRINCIPAL_SELF = "S-1-5-10"
|
||||
SID_AUTHENTICATED_USERS = "S-1-5-11"
|
||||
SID_RESTRICTED_CODE = "S-1-5-12"
|
||||
SID_TERMINAL_SERVER_USER = "S-1-5-13"
|
||||
SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
|
||||
SID_THIS_ORGANIZATION = "S-1-5-15"
|
||||
SID_IUSR = "S-1-5-17"
|
||||
SID_LOCAL_SYSTEM = "S-1-5-18"
|
||||
SID_LOCAL_SERVICE = "S-1-5-19"
|
||||
SID_NETWORK_SERVICE = "S-1-5-20"
|
||||
SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
|
||||
SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
|
||||
SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
|
||||
SID_BUILTIN_USERS = "S-1-5-32-545"
|
||||
SID_BUILTIN_GUESTS = "S-1-5-32-546"
|
||||
SID_POWER_USERS = "S-1-5-32-547"
|
||||
SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
|
||||
SID_SERVER_OPERATORS = "S-1-5-32-549"
|
||||
SID_PRINTER_OPERATORS = "S-1-5-32-550"
|
||||
SID_BACKUP_OPERATORS = "S-1-5-32-551"
|
||||
SID_REPLICATOR = "S-1-5-32-552"
|
||||
SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
|
||||
SID_REMOTE_DESKTOP = "S-1-5-32-555"
|
||||
SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
|
||||
SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
|
||||
SID_PERFMON_USERS = "S-1-5-32-558"
|
||||
SID_PERFLOG_USERS = "S-1-5-32-559"
|
||||
SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
|
||||
SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
|
||||
SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
|
||||
SID_IIS_IUSRS = "S-1-5-32-568"
|
||||
SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
|
||||
SID_EVENT_LOG_READERS = "S-1-5-32-573"
|
||||
SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
|
||||
SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
|
||||
SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
|
||||
SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
|
||||
SID_HYPER_V_ADMINS = "S-1-5-32-578"
|
||||
SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
|
||||
SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
|
||||
SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
|
||||
SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
|
||||
SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
|
||||
SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
|
||||
SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
|
||||
SID_NT_SERVICE = "S-1-5-80"
|
||||
SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
|
||||
SID_LOCAL_ACCOUNT = "S-1-5-113"
|
||||
SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
|
||||
SID_OTHER_ORGANIZATION = "S-1-5-1000"
|
||||
SID_ALL_APP_PACKAGES = "S-1-15-2-1"
|
||||
SID_ML_UNTRUSTED = "S-1-16-0"
|
||||
SID_ML_LOW = "S-1-16-4096"
|
||||
SID_ML_MEDIUM = "S-1-16-8192"
|
||||
SID_ML_MEDIUM_PLUS = "S-1-16-8448"
|
||||
SID_ML_HIGH = "S-1-16-12288"
|
||||
SID_ML_SYSTEM = "S-1-16-16384"
|
||||
SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
|
||||
SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
|
||||
SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
|
||||
SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
|
||||
SID_KEY_TRUST_IDENTITY = "S-1-18-4"
|
||||
SID_KEY_PROPERTY_MFA = "S-1-18-5"
|
||||
SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
|
||||
|
||||
|
||||
class SID(object):
|
||||
"""
|
||||
A Windows security identifier. Represents a single principal, such a
|
||||
user or a group, as a sequence of numbers consisting of the revision,
|
||||
identifier authority, and a variable-length list of subauthorities.
|
||||
|
||||
See [MS-DTYP]: 2.4.2
|
||||
"""
|
||||
def __init__(self, revision, identifier_authority, subauthorities):
|
||||
#: Revision, should always be 1.
|
||||
self.revision = revision
|
||||
#: An integer representing the identifier authority.
|
||||
self.identifier_authority = identifier_authority
|
||||
#: A list of integers representing all subauthorities.
|
||||
self.subauthorities = subauthorities
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation, as specified in [MS-DTYP]: 2.4.2.1
|
||||
"""
|
||||
if self.identifier_authority >= 2**32:
|
||||
id_auth = '%#x' % (self.identifier_authority,)
|
||||
else:
|
||||
id_auth = self.identifier_authority
|
||||
auths = [self.revision, id_auth] + self.subauthorities
|
||||
return 'S-' + '-'.join(str(subauth) for subauth in auths)
|
||||
|
||||
def __repr__(self):
|
||||
return 'SID(%r)' % (str(self),)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data, return_tail=False):
|
||||
revision, subauth_count = struct.unpack('<BB', data[:2])
|
||||
identifier_authority = struct.unpack('>Q', b'\x00\x00' + data[2:8])[0]
|
||||
subauth_data = data[8:]
|
||||
subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
|
||||
for i in range(subauth_count)]
|
||||
sid = cls(revision, identifier_authority, subauthorities)
|
||||
if return_tail:
|
||||
return sid, subauth_data[4 * subauth_count :]
|
||||
return sid
|
||||
|
||||
|
||||
class ACE(object):
|
||||
"""
|
||||
Represents a single access control entry.
|
||||
|
||||
See [MS-DTYP]: 2.4.4
|
||||
"""
|
||||
HEADER_FORMAT = '<BBH'
|
||||
|
||||
def __init__(self, type_, flags, mask, sid, additional_data):
|
||||
#: An integer representing the type of the ACE. One of the
|
||||
#: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
|
||||
#: from [MS-DTYP] 2.4.4.1.
|
||||
self.type = type_
|
||||
#: An integer bitmask with ACE flags, corresponds to the
|
||||
#: ``AceFlags`` field.
|
||||
self.flags = flags
|
||||
#: An integer representing the ``ACCESS_MASK`` as specified in
|
||||
#: [MS-DTYP] 2.4.3.
|
||||
self.mask = mask
|
||||
#: The :class:`SID` of a trustee.
|
||||
self.sid = sid
|
||||
#: A dictionary of additional fields present in the ACE, depending
|
||||
#: on the type. The following fields can be present:
|
||||
#:
|
||||
#: * ``flags``
|
||||
#: * ``object_type``
|
||||
#: * ``inherited_object_type``
|
||||
#: * ``application_data``
|
||||
#: * ``attribute_data``
|
||||
self.additional_data = additional_data
|
||||
|
||||
def __repr__(self):
|
||||
return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
|
||||
self.type, self.flags, self.mask, self.sid,
|
||||
)
|
||||
|
||||
@property
|
||||
def isInheritOnly(self):
|
||||
"""Convenience property which indicates if this ACE is inherit
|
||||
only, meaning that it doesn't apply to the object itself."""
|
||||
return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header = data[:header_size]
|
||||
type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
body = data[header_size:size]
|
||||
additional_data = {}
|
||||
|
||||
# In all ACE types, the mask immediately follows the header.
|
||||
mask = struct.unpack('<I', body[:4])[0]
|
||||
body = body[4:]
|
||||
|
||||
# All OBJECT-type ACEs contain additional flags, and two GUIDs as
|
||||
# the following fields.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['flags'] = struct.unpack('<I', body[:4])[0]
|
||||
additional_data['object_type'] = body[4:20]
|
||||
additional_data['inherited_object_type'] = body[20:36]
|
||||
body = body[36:]
|
||||
|
||||
# Then the SID in all types.
|
||||
sid, body = SID.from_bytes(body, return_tail=True)
|
||||
|
||||
# CALLBACK-type ACEs (and for some obscure reason,
|
||||
# SYSTEM_AUDIT_OBJECT) have a final tail of application data.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['application_data'] = body
|
||||
|
||||
# SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
|
||||
if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
|
||||
additional_data['attribute_data'] = body
|
||||
|
||||
return cls(type_, flags, mask, sid, additional_data)
|
||||
|
||||
|
||||
class ACL(object):
|
||||
"""
|
||||
Access control list, encapsulating a sequence of access control
|
||||
entries.
|
||||
|
||||
See [MS-DTYP]: 2.4.5
|
||||
"""
|
||||
HEADER_FORMAT = '<BBHHH'
|
||||
|
||||
def __init__(self, revision, aces):
|
||||
#: Integer value of the revision.
|
||||
self.revision = revision
|
||||
#: List of :class:`ACE` instances.
|
||||
self.aces = aces
|
||||
|
||||
def __repr__(self):
|
||||
return "ACL(%r)" % (self.aces,)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
revision = None
|
||||
aces = []
|
||||
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header, remaining = data[:header_size], data[header_size:]
|
||||
revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
for i in range(count):
|
||||
ace_size = struct.unpack('<H', remaining[2:4])[0]
|
||||
ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
|
||||
aces.append(ACE.from_bytes(ace_data))
|
||||
|
||||
return cls(revision, aces)
|
||||
|
||||
|
||||
class SecurityDescriptor(object):
|
||||
"""
|
||||
Represents a security descriptor.
|
||||
|
||||
See [MS-DTYP]: 2.4.6
|
||||
"""
|
||||
|
||||
HEADER_FORMAT = '<BBHIIII'
|
||||
|
||||
def __init__(self, flags, owner, group, dacl, sacl):
|
||||
#: Integer bitmask of control flags. Corresponds to the
|
||||
#: ``Control`` field in [MS-DTYP] 2.4.6.
|
||||
self.flags = flags
|
||||
#: Instance of :class:`SID` representing the owner user.
|
||||
self.owner = owner
|
||||
#: Instance of :class:`SID` representing the owner group.
|
||||
self.group = group
|
||||
#: Instance of :class:`ACL` representing the discretionary access
|
||||
#: control list, which specifies access restrictions of an object.
|
||||
self.dacl = dacl
|
||||
#: Instance of :class:`ACL` representing the system access control
|
||||
#: list, which specifies audit logging of an object.
|
||||
self.sacl = sacl
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
owner = None
|
||||
group = None
|
||||
dacl = None
|
||||
sacl = None
|
||||
|
||||
header = data[:struct.calcsize(cls.HEADER_FORMAT)]
|
||||
(revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
|
||||
dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert revision == 1
|
||||
assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
|
||||
for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
|
||||
assert 0 <= offset < len(data)
|
||||
|
||||
if owner_offset:
|
||||
owner = SID.from_bytes(data[owner_offset:])
|
||||
if group_offset:
|
||||
group = SID.from_bytes(data[group_offset:])
|
||||
if dacl_offset:
|
||||
dacl = ACL.from_bytes(data[dacl_offset:])
|
||||
if sacl_offset:
|
||||
sacl = ACL.from_bytes(data[sacl_offset:])
|
||||
|
||||
return cls(flags, owner, group, dacl, sacl)
|
||||
136
lib/sambatools/python3/smb/securityblob.py
Normal file
136
lib/sambatools/python3/smb/securityblob.py
Normal file
@@ -0,0 +1,136 @@
|
||||
|
||||
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
|
||||
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
||||
|
||||
|
||||
class UnsupportedSecurityProvider(Exception): pass
|
||||
class BadSecurityBlobError(Exception): pass
|
||||
|
||||
|
||||
def generateNegotiateSecurityBlob(ntlm_data):
|
||||
mech_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
mech_types = MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
mech_types.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.4.1.311.2.2.10'))
|
||||
|
||||
n = NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
n.setComponentByName('mechTypes', mech_types)
|
||||
n.setComponentByName('mechToken', mech_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenInit', n)
|
||||
|
||||
ct = ContextToken()
|
||||
ct.setComponentByName('thisMech', univ.ObjectIdentifier('1.3.6.1.5.5.2'))
|
||||
ct.setComponentByName('innerContextToken', nt)
|
||||
|
||||
return encoder.encode(ct)
|
||||
|
||||
|
||||
def generateAuthSecurityBlob(ntlm_data):
|
||||
response_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
|
||||
n = NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||
n.setComponentByName('responseToken', response_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenTarg', n)
|
||||
|
||||
return encoder.encode(nt)
|
||||
|
||||
|
||||
def decodeChallengeSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
token = nt.getComponentByName('responseToken')
|
||||
if not token:
|
||||
raise BadSecurityBlobError('NTLMSSP_CHALLENGE security blob does not contain responseToken field')
|
||||
|
||||
provider_oid = nt.getComponentByName('supportedMech')
|
||||
if provider_oid and str(provider_oid) != '1.3.6.1.4.1.311.2.2.10': # This OID is defined in [MS-NLMP]: 1.9
|
||||
raise UnsupportedSecurityProvider('Security provider "%s" is not supported by pysmb' % str(provider_oid))
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result), token
|
||||
except Exception as ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
def decodeAuthResponseSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result)
|
||||
except Exception as ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
#
|
||||
# GSS-API ASN.1 (RFC2478 section 3.2.1)
|
||||
#
|
||||
|
||||
RESULT_ACCEPT_COMPLETED = 0
|
||||
RESULT_ACCEPT_INCOMPLETE = 1
|
||||
RESULT_REJECT = 2
|
||||
|
||||
class NegResultEnumerated(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'accept_completed', 0 ),
|
||||
( 'accept_incomplete', 1 ),
|
||||
( 'reject', 2 )
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2)
|
||||
|
||||
|
||||
class MechTypeList(univ.SequenceOf):
|
||||
componentType = univ.ObjectIdentifier()
|
||||
|
||||
|
||||
class ContextFlags(univ.BitString):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'delegFlag', 0 ),
|
||||
( 'mutualFlag', 1 ),
|
||||
( 'replayFlag', 2 ),
|
||||
( 'sequenceFlag', 3 ),
|
||||
( 'anonFlag', 4 ),
|
||||
( 'confFlag', 5 ),
|
||||
( 'integFlag', 6 )
|
||||
)
|
||||
|
||||
|
||||
class NegTokenInit(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('mechTypes', MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('reqFlags', ContextFlags().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('mechToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegTokenTarg(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('negResult', NegResultEnumerated().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('supportedMech', univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('responseToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegotiationToken(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.NamedType('negTokenTarg', NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||
)
|
||||
|
||||
|
||||
class ContextToken(univ.Sequence):
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('thisMech', univ.ObjectIdentifier()),
|
||||
namedtype.NamedType('innerContextToken', NegotiationToken())
|
||||
)
|
||||
101
lib/sambatools/python3/smb/smb2_constants.py
Normal file
101
lib/sambatools/python3/smb/smb2_constants.py
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
# Bitmask for Flags field in SMB2 message header
|
||||
SMB2_FLAGS_SERVER_TO_REDIR = 0x01
|
||||
SMB2_FLAGS_ASYNC_COMMAND = 0x02
|
||||
SMB2_FLAGS_RELATED_OPERATIONS = 0x04
|
||||
SMB2_FLAGS_SIGNED = 0x08
|
||||
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000
|
||||
|
||||
# Values for Command field in SMB2 message header
|
||||
SMB2_COM_NEGOTIATE = 0x0000
|
||||
SMB2_COM_SESSION_SETUP = 0x0001
|
||||
SMB2_COM_LOGOFF = 0x0002
|
||||
SMB2_COM_TREE_CONNECT = 0x0003
|
||||
SMB2_COM_TREE_DISCONNECT = 0x0004
|
||||
SMB2_COM_CREATE = 0x0005
|
||||
SMB2_COM_CLOSE = 0x0006
|
||||
SMB2_COM_FLUSH = 0x0007
|
||||
SMB2_COM_READ = 0x0008
|
||||
SMB2_COM_WRITE = 0x0009
|
||||
SMB2_COM_LOCK = 0x000A
|
||||
SMB2_COM_IOCTL = 0x000B
|
||||
SMB2_COM_CANCEL = 0x000C
|
||||
SMB2_COM_ECHO = 0x000D
|
||||
SMB2_COM_QUERY_DIRECTORY = 0x000E
|
||||
SMB2_COM_CHANGE_NOTIFY = 0x000F
|
||||
SMB2_COM_QUERY_INFO = 0x0010
|
||||
SMB2_COM_SET_INFO = 0x0011
|
||||
SMB2_COM_OPLOCK_BREAK = 0x0012
|
||||
|
||||
SMB2_COMMAND_NAMES = {
|
||||
0x0000: 'SMB2_COM_NEGOTIATE',
|
||||
0x0001: 'SMB2_COM_SESSION_SETUP',
|
||||
0x0002: 'SMB2_COM_LOGOFF',
|
||||
0x0003: 'SMB2_COM_TREE_CONNECT',
|
||||
0x0004: 'SMB2_COM_TREE_DISCONNECT',
|
||||
0x0005: 'SMB2_COM_CREATE',
|
||||
0x0006: 'SMB2_COM_CLOSE',
|
||||
0x0007: 'SMB2_COM_FLUSH',
|
||||
0x0008: 'SMB2_COM_READ',
|
||||
0x0009: 'SMB2_COM_WRITE',
|
||||
0x000A: 'SMB2_COM_LOCK',
|
||||
0x000B: 'SMB2_COM_IOCTL',
|
||||
0x000C: 'SMB2_COM_CANCEL',
|
||||
0x000D: 'SMB2_COM_ECHO',
|
||||
0x000E: 'SMB2_COM_QUERY_DIRECTORY',
|
||||
0x000F: 'SMB2_COM_CHANGE_NOTIFY',
|
||||
0x0010: 'SMB2_COM_QUERY_INFO',
|
||||
0x0011: 'SMB2_COM_SET_INFO',
|
||||
0x0012: 'SMB2_COM_OPLOCK_BREAK',
|
||||
}
|
||||
|
||||
# Values for dialect_revision field in SMB2NegotiateResponse class
|
||||
SMB2_DIALECT_2 = 0x0202
|
||||
SMB2_DIALECT_21 = 0x0210
|
||||
SMB2_DIALECT_2ALL = 0x02FF
|
||||
|
||||
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
||||
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002
|
||||
|
||||
# Values for ShareType field in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_TYPE_DISK = 0x01
|
||||
SMB2_SHARE_TYPE_PIPE = 0x02
|
||||
SMB2_SHARE_TYPE_PRINTER = 0x03
|
||||
|
||||
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_CAP_DFS = 0x0008
|
||||
|
||||
# Values for OpLockLevel field in SMB2CreateRequest class
|
||||
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
||||
SMB2_OPLOCK_LEVEL_II = 0x01
|
||||
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
|
||||
SMB2_OPLOCK_LEVEL_BATCH = 0x09
|
||||
SMB2_OPLOCK_LEVEL_LEASE = 0xFF
|
||||
|
||||
# Values for FileAttributes field in SMB2CreateRequest class
|
||||
# The values are defined in [MS-FSCC] 2.6
|
||||
SMB2_FILE_ATTRIBUTE_ARCHIVE = 0x0020
|
||||
SMB2_FILE_ATTRIBUTE_COMPRESSED = 0x0800
|
||||
SMB2_FILE_ATTRIBUTE_DIRECTORY = 0x0010
|
||||
SMB2_FILE_ATTRIBUTE_ENCRYPTED = 0x4000
|
||||
SMB2_FILE_ATTRIBUTE_HIDDEN = 0x0002
|
||||
SMB2_FILE_ATTRIBUTE_NORMAL = 0x0080
|
||||
SMB2_FILE_ATTRIBUTE_NOTINDEXED = 0x2000
|
||||
SMB2_FILE_ATTRIBUTE_OFFLINE = 0x1000
|
||||
SMB2_FILE_ATTRIBUTE_READONLY = 0x0001
|
||||
SMB2_FILE_ATTRIBUTE_SPARSE = 0x0200
|
||||
SMB2_FILE_ATTRIBUTE_SYSTEM = 0x0004
|
||||
SMB2_FILE_ATTRIBUTE_TEMPORARY = 0x0100
|
||||
|
||||
# Values for CreateAction field in SMB2CreateResponse class
|
||||
SMB2_FILE_SUPERCEDED = 0x00
|
||||
SMB2_FILE_OPENED = 0x01
|
||||
SMB2_FILE_CREATED = 0x02
|
||||
SMB2_FILE_OVERWRITTEN = 0x03
|
||||
|
||||
# Values for InfoType field in SMB2QueryInfoRequest class
|
||||
SMB2_INFO_FILE = 0x01
|
||||
SMB2_INFO_FILESYSTEM = 0x02
|
||||
SMB2_INFO_SECURITY = 0x03
|
||||
SMB2_INFO_QUOTA = 0x04
|
||||
857
lib/sambatools/python3/smb/smb2_structs.py
Normal file
857
lib/sambatools/python3/smb/smb2_structs.py
Normal file
@@ -0,0 +1,857 @@
|
||||
|
||||
import os, sys, struct, types, logging, binascii, time
|
||||
from io import StringIO
|
||||
from .smb_structs import ProtocolError
|
||||
from .smb_constants import *
|
||||
from .smb2_constants import *
|
||||
from .utils import convertFILETIMEtoEpoch
|
||||
|
||||
|
||||
class SMB2Message:
|
||||
|
||||
HEADER_STRUCT_FORMAT = "<4sHHIHHI" # This refers to the common header part that is shared by both sync and async SMB2 header
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
ASYNC_HEADER_STRUCT_FORMAT = "<IQQQ16s"
|
||||
ASYNC_HEADER_STRUCT_SIZE = struct.calcsize(ASYNC_HEADER_STRUCT_FORMAT)
|
||||
|
||||
SYNC_HEADER_STRUCT_FORMAT = "<IQIIQ16s"
|
||||
SYNC_HEADER_STRUCT_SIZE = struct.calcsize(SYNC_HEADER_STRUCT_FORMAT)
|
||||
|
||||
HEADER_SIZE = 64
|
||||
|
||||
log = logging.getLogger('SMB.SMB2Message')
|
||||
protocol = 2
|
||||
|
||||
def __init__(self, payload = None):
|
||||
self.reset()
|
||||
if payload:
|
||||
self.payload = payload
|
||||
self.payload.initMessage(self)
|
||||
|
||||
def __str__(self):
|
||||
b = StringIO()
|
||||
b.write('Command: 0x%02X (%s) %s' % ( self.command, SMB2_COMMAND_NAMES.get(self.command, '<unknown>'), os.linesep ))
|
||||
b.write('Status: 0x%08X %s' % ( self.status, os.linesep ))
|
||||
b.write('Flags: 0x%02X %s' % ( self.flags, os.linesep ))
|
||||
b.write('PID: %d %s' % ( self.pid, os.linesep ))
|
||||
b.write('MID: %d %s' % ( self.mid, os.linesep ))
|
||||
b.write('TID: %d %s' % ( self.tid, os.linesep ))
|
||||
b.write('Data: %d bytes %s%s %s' % ( len(self.data), os.linesep, str(binascii.hexlify(self.data)), os.linesep ))
|
||||
return b.getvalue()
|
||||
|
||||
def reset(self):
|
||||
self.raw_data = b''
|
||||
self.command = 0
|
||||
self.status = 0
|
||||
self.flags = 0
|
||||
|
||||
self.next_command_offset = 0
|
||||
self.mid = 0
|
||||
self.session_id = 0
|
||||
self.signature = b'\0'*16
|
||||
self.payload = None
|
||||
self.data = b''
|
||||
|
||||
# For async SMB2 message
|
||||
self.async_id = 0
|
||||
|
||||
# For sync SMB2 message
|
||||
self.pid = 0
|
||||
self.tid = 0
|
||||
|
||||
# Not used in this class. Maintained for compatibility with SMBMessage class
|
||||
self.flags2 = 0
|
||||
self.uid = 0
|
||||
self.security = 0
|
||||
self.parameters_data = b''
|
||||
|
||||
def encode(self):
|
||||
"""
|
||||
Encode this SMB2 message into a series of bytes suitable to be embedded with a NetBIOS session message.
|
||||
AssertionError will be raised if this SMB message has not been initialized with a Payload instance
|
||||
|
||||
@return: a string containing the encoded SMB2 message
|
||||
"""
|
||||
assert self.payload
|
||||
|
||||
self.pid = os.getpid()
|
||||
self.payload.prepare(self)
|
||||
|
||||
headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
b'\xFESMB', self.HEADER_SIZE, 0, self.status, self.command, 0, self.flags) + \
|
||||
struct.pack(self.SYNC_HEADER_STRUCT_FORMAT, self.next_command_offset, self.mid, self.pid, self.tid, self.session_id, self.signature)
|
||||
return headers_data + self.data
|
||||
|
||||
def decode(self, buf):
|
||||
"""
|
||||
Decodes the SMB message in buf.
|
||||
All fields of the SMB2Message object will be reset to default values before decoding.
|
||||
On errors, do not assume that the fields will be reinstated back to what they are before
|
||||
this method is invoked.
|
||||
|
||||
References
|
||||
==========
|
||||
- [MS-SMB2]: 2.2.1
|
||||
|
||||
@param buf: data containing one complete SMB2 message
|
||||
@type buf: string
|
||||
@return: a positive integer indicating the number of bytes used in buf to decode this SMB message
|
||||
@raise ProtocolError: raised when decoding fails
|
||||
"""
|
||||
buf_len = len(buf)
|
||||
if buf_len < 64: # All SMB2 headers must be at least 64 bytes. [MS-SMB2]: 2.2.1.1, 2.2.1.2
|
||||
raise ProtocolError('Not enough data to decode SMB2 header', buf)
|
||||
|
||||
self.reset()
|
||||
|
||||
protocol, struct_size, self.credit_charge, self.status, \
|
||||
self.command, self.credit_re, self.flags = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
if protocol != b'\xFESMB':
|
||||
raise ProtocolError('Invalid 4-byte SMB2 protocol field', buf)
|
||||
|
||||
if struct_size != self.HEADER_SIZE:
|
||||
raise ProtocolError('Invalid SMB2 header structure size')
|
||||
|
||||
if self.isAsync:
|
||||
if buf_len < self.HEADER_STRUCT_SIZE+self.ASYNC_HEADER_STRUCT_SIZE:
|
||||
raise ProtocolError('Not enough data to decode SMB2 header', buf)
|
||||
|
||||
self.next_command_offset, self.mid, self.async_id, self.session_id, \
|
||||
self.signature = struct.unpack(self.ASYNC_HEADER_STRUCT_FORMAT,
|
||||
buf[self.HEADER_STRUCT_SIZE:self.HEADER_STRUCT_SIZE+self.ASYNC_HEADER_STRUCT_SIZE])
|
||||
else:
|
||||
if buf_len < self.HEADER_STRUCT_SIZE+self.SYNC_HEADER_STRUCT_SIZE:
|
||||
raise ProtocolError('Not enough data to decode SMB2 header', buf)
|
||||
|
||||
self.next_command_offset, self.mid, self.pid, self.tid, self.session_id, \
|
||||
self.signature = struct.unpack(self.SYNC_HEADER_STRUCT_FORMAT,
|
||||
buf[self.HEADER_STRUCT_SIZE:self.HEADER_STRUCT_SIZE+self.SYNC_HEADER_STRUCT_SIZE])
|
||||
|
||||
if self.next_command_offset > 0:
|
||||
self.raw_data = buf[:self.next_command_offset]
|
||||
self.data = buf[self.HEADER_SIZE:self.next_command_offset]
|
||||
else:
|
||||
self.raw_data = buf
|
||||
self.data = buf[self.HEADER_SIZE:]
|
||||
|
||||
self._decodeCommand()
|
||||
if self.payload:
|
||||
self.payload.decode(self)
|
||||
|
||||
return len(self.raw_data)
|
||||
|
||||
def _decodeCommand(self):
|
||||
if self.command == SMB2_COM_READ:
|
||||
self.payload = SMB2ReadResponse()
|
||||
elif self.command == SMB2_COM_WRITE:
|
||||
self.payload = SMB2WriteResponse()
|
||||
elif self.command == SMB2_COM_QUERY_DIRECTORY:
|
||||
self.payload = SMB2QueryDirectoryResponse()
|
||||
elif self.command == SMB2_COM_CREATE:
|
||||
self.payload = SMB2CreateResponse()
|
||||
elif self.command == SMB2_COM_CLOSE:
|
||||
self.payload = SMB2CloseResponse()
|
||||
elif self.command == SMB2_COM_QUERY_INFO:
|
||||
self.payload = SMB2QueryInfoResponse()
|
||||
elif self.command == SMB2_COM_SET_INFO:
|
||||
self.payload = SMB2SetInfoResponse()
|
||||
elif self.command == SMB2_COM_IOCTL:
|
||||
self.payload = SMB2IoctlResponse()
|
||||
elif self.command == SMB2_COM_TREE_CONNECT:
|
||||
self.payload = SMB2TreeConnectResponse()
|
||||
elif self.command == SMB2_COM_SESSION_SETUP:
|
||||
self.payload = SMB2SessionSetupResponse()
|
||||
elif self.command == SMB2_COM_NEGOTIATE:
|
||||
self.payload = SMB2NegotiateResponse()
|
||||
elif self.command == SMB2_COM_ECHO:
|
||||
self.payload = SMB2EchoResponse()
|
||||
|
||||
@property
|
||||
def isAsync(self):
|
||||
return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND)
|
||||
|
||||
@property
|
||||
def isReply(self):
|
||||
return bool(self.flags & SMB2_FLAGS_SERVER_TO_REDIR)
|
||||
|
||||
|
||||
class Structure:
|
||||
|
||||
def initMessage(self, message):
|
||||
pass
|
||||
|
||||
def prepare(self, message):
|
||||
raise NotImplementedError
|
||||
|
||||
def decode(self, message):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SMB2NegotiateResponse(Structure):
|
||||
"""
|
||||
Contains information on the SMB2_NEGOTIATE response from server
|
||||
|
||||
After calling the decode method, each instance will contain the following attributes,
|
||||
- security_mode (integer)
|
||||
- dialect_revision (integer)
|
||||
- server_guid (string)
|
||||
- max_transact_size (integer)
|
||||
- max_read_size (integer)
|
||||
- max_write_size (integer)
|
||||
- system_time (long)
|
||||
- server_start_time (long)
|
||||
- security_blob (string)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.4
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHHH16sIIIIQQHHI"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_NEGOTIATE
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, self.security_mode, self.dialect_revision, _, self.server_guid, self.capabilities, \
|
||||
self.max_transact_size, self.max_read_size, self.max_write_size, self.system_time, self.server_start_time, \
|
||||
security_buf_offset, security_buf_len, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
self.server_start_time = convertFILETIMEtoEpoch(self.server_start_time)
|
||||
self.system_time = convertFILETIMEtoEpoch(self.system_time)
|
||||
self.security_blob = message.raw_data[security_buf_offset:security_buf_offset+security_buf_len]
|
||||
|
||||
|
||||
class SMB2SessionSetupRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.5
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIIHHQ"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, security_blob):
|
||||
self.security_blob = security_blob
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_SESSION_SETUP
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
25, # Structure size. Must be 25 as mandated by [MS-SMB2] 2.2.5
|
||||
0, # VcNumber
|
||||
0x01, # Security mode
|
||||
0x00, # Capabilities
|
||||
0, # Channel
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE,
|
||||
len(self.security_blob),
|
||||
0) + self.security_blob
|
||||
|
||||
|
||||
class SMB2SessionSetupResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_COM_SESSION_SETUP response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- session_flags (integer)
|
||||
- security_blob (string)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.6
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHHH"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
@property
|
||||
def isGuestSession(self):
|
||||
return (self.session_flags & 0x0001) > 0 # SMB2_SESSION_FLAG_IS_GUEST
|
||||
|
||||
@property
|
||||
def isAnonymousSession(self):
|
||||
return (self.session_flags & 0x0002) > 0 # SMB2_SESSION_FLAG_IS_NULL
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_SESSION_SETUP
|
||||
|
||||
struct_size, self.session_flags, security_blob_offset, security_blob_len \
|
||||
= struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
self.security_blob = message.raw_data[security_blob_offset:security_blob_offset+security_blob_len]
|
||||
|
||||
|
||||
class SMB2TreeConnectRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.9
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHHH"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_TREE_CONNECT
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
9, # Structure size. Must be 9 as mandated by [MS-SMB2] 2.2.9
|
||||
0, # Reserved
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE,
|
||||
len(self.path)*2) + self.path.encode('UTF-16LE')
|
||||
|
||||
|
||||
class SMB2TreeConnectResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_COM_TREE_CONNECT response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- share_type (integer): one of the SMB2_SHARE_TYPE_xxx constants
|
||||
- share_flags (integer)
|
||||
- capabilities (integer): bitmask of SMB2_SHARE_CAP_xxx
|
||||
- maximal_access (integer)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.10
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_TREE_CONNECT
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, self.share_type, _, \
|
||||
self.share_flags, self.capabilities, self.maximal_access \
|
||||
= struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
|
||||
class SMB2CreateRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.13
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIQQIIIIIHHII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, filename, file_attributes = 0,
|
||||
access_mask = 0, share_access = 0, create_disp = 0, create_options = 0,
|
||||
impersonation = SEC_ANONYMOUS,
|
||||
oplock = SMB2_OPLOCK_LEVEL_NONE,
|
||||
create_context_data = b''):
|
||||
self.filename = filename
|
||||
self.file_attributes = file_attributes
|
||||
self.access_mask = access_mask
|
||||
self.share_access = share_access
|
||||
self.create_disp = create_disp
|
||||
self.create_options = create_options
|
||||
self.oplock = oplock
|
||||
self.impersonation = impersonation
|
||||
self.create_context_data = create_context_data or b''
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_CREATE
|
||||
|
||||
def prepare(self, message):
|
||||
buf = self.filename.encode('UTF-16LE')
|
||||
filename_len = len(buf)
|
||||
if self.create_context_data:
|
||||
n = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf)
|
||||
if n % 8 != 0:
|
||||
buf += b'\0'*(8-n%8)
|
||||
create_context_offset = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf)
|
||||
else:
|
||||
create_context_offset = n
|
||||
buf += self.create_context_data
|
||||
else:
|
||||
create_context_offset = 0
|
||||
if not buf:
|
||||
buf = b'\0'
|
||||
|
||||
assert create_context_offset % 8 == 0
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
57, # Structure size. Must be 57 as mandated by [MS-SMB2] 2.2.13
|
||||
0, # SecurityFlag. Must be 0
|
||||
self.oplock,
|
||||
self.impersonation,
|
||||
0, # SmbCreateFlags. Must be 0
|
||||
0, # Reserved. Must be 0
|
||||
self.access_mask, # DesiredAccess. [MS-SMB2] 2.2.13.1
|
||||
self.file_attributes,
|
||||
self.share_access,
|
||||
self.create_disp,
|
||||
self.create_options,
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # NameOffset
|
||||
filename_len, # Length of encoded filename in bytes
|
||||
create_context_offset, # CreateContextOffset
|
||||
len(self.create_context_data) # CreateContextLength
|
||||
) + buf
|
||||
|
||||
class SMB2CreateResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_COM_CREATE response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- oplock (integer): one of SMB2_OPLOCK_LEVEL_xxx constants
|
||||
- create_action (integer): one of SMB2_FILE_xxx constants
|
||||
- allocation_size (long)
|
||||
- file_size (long)
|
||||
- file_attributes (integer)
|
||||
- fid (16-bytes string)
|
||||
- create_time, lastaccess_time, lastwrite_time, change_time (float)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.14
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIQQQQQQII16sII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_CREATE
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, self.oplock, _, self.create_action, \
|
||||
create_time, lastaccess_time, lastwrite_time, change_time, \
|
||||
self.allocation_size, self.file_size, self.file_attributes, \
|
||||
_, self.fid, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
self.create_time = convertFILETIMEtoEpoch(create_time)
|
||||
self.lastaccess_time = convertFILETIMEtoEpoch(lastaccess_time)
|
||||
self.lastwrite_time = convertFILETIMEtoEpoch(lastwrite_time)
|
||||
self.change_time = convertFILETIMEtoEpoch(change_time)
|
||||
|
||||
|
||||
class SMB2WriteRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.21
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHIQ16sIIHHI"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, data, offset, remaining_len = 0, flags = 0):
|
||||
assert len(fid) == 16
|
||||
self.fid = fid
|
||||
self.data = data
|
||||
self.offset = offset
|
||||
self.remaining_len = remaining_len
|
||||
self.flags = flags
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_WRITE
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
49, # Structure size. Must be 49 as mandated by [MS-SMB2] 2.2.21
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # DataOffset
|
||||
len(self.data),
|
||||
self.offset,
|
||||
self.fid,
|
||||
0, # Channel. Must be 0
|
||||
self.remaining_len, # RemainingBytes
|
||||
0, # WriteChannelInfoOffset,
|
||||
0, # WriteChannelInfoLength
|
||||
self.flags) + self.data
|
||||
|
||||
|
||||
class SMB2WriteResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_WRITE response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- count (integer)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.22
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHIIHH"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_WRITE
|
||||
if message.status == 0:
|
||||
struct_size, _, self.count, _, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
|
||||
|
||||
class SMB2ReadRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.19
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIQ16sIIIHH"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, read_offset, read_len, min_read_len = 0):
|
||||
self.fid = fid
|
||||
self.read_offset = read_offset
|
||||
self.read_len = read_len
|
||||
self.min_read_len = min_read_len
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_READ
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
49, # Structure size. Must be 49 as mandated by [MS-SMB2] 2.2.19
|
||||
0, # Padding
|
||||
0, # Reserved
|
||||
self.read_len,
|
||||
self.read_offset,
|
||||
self.fid,
|
||||
self.min_read_len,
|
||||
0, # Channel
|
||||
0, # RemainingBytes
|
||||
0, # ReadChannelInfoOffset
|
||||
0 # ReadChannelInfoLength
|
||||
) + b'\0'
|
||||
|
||||
|
||||
class SMB2ReadResponse(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.20
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_READ
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, data_offset, _, self.data_length, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
self.data = message.raw_data[data_offset:data_offset+self.data_length]
|
||||
|
||||
|
||||
class SMB2IoctlRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.31
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHI16sIIIIIIII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, ctlcode, flags, in_data, max_out_size = 65536):
|
||||
self.ctlcode = ctlcode
|
||||
self.fid = fid
|
||||
self.flags = flags
|
||||
self.in_data = in_data
|
||||
self.max_out_size = max_out_size
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_IOCTL
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
57, # Structure size. Must be 57 as mandated by [MS-SMB2] 2.2.31
|
||||
0, # Reserved
|
||||
self.ctlcode, # CtlCode
|
||||
self.fid,
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # InputOffset
|
||||
len(self.in_data), # InputCount
|
||||
0, # MaxInputResponse
|
||||
0, # OutputOffset
|
||||
0, # OutputCount
|
||||
self.max_out_size, # MaxOutputResponse
|
||||
self.flags, # Flags
|
||||
0 # Reserved
|
||||
) + self.in_data
|
||||
|
||||
|
||||
class SMB2IoctlResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_IOCTL response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- ctlcode (integer)
|
||||
- fid (16-bytes string)
|
||||
- flags (integer)
|
||||
- in_data (string)
|
||||
- out_data (string)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.32
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHI16sIIIIII"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_IOCTL
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, _, self.ctlcode, self.fid, \
|
||||
input_offset, input_len, output_offset, output_len, \
|
||||
self.flags, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
|
||||
if input_len > 0:
|
||||
self.in_data = message.raw_data[input_offset:input_offset+input_len]
|
||||
else:
|
||||
self.in_data = b''
|
||||
|
||||
if output_len > 0:
|
||||
self.out_data = message.raw_data[output_offset:output_offset+output_len]
|
||||
else:
|
||||
self.out_data = b''
|
||||
|
||||
|
||||
class SMB2CloseRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.15
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHI16s"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, flags = 0):
|
||||
self.fid = fid
|
||||
self.flags = flags
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_CLOSE
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
24, # Structure size. Must be 24 as mandated by [MS-SMB2]: 2.2.15
|
||||
self.flags,
|
||||
0, # Reserved. Must be 0
|
||||
self.fid)
|
||||
|
||||
|
||||
class SMB2CloseResponse(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.16
|
||||
"""
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_CLOSE
|
||||
|
||||
|
||||
class SMB2QueryDirectoryRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.33
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBI16sHHI"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, filename, info_class, flags, output_buf_len):
|
||||
self.fid = fid
|
||||
self.filename = filename
|
||||
self.info_class = info_class
|
||||
self.flags = flags
|
||||
self.output_buf_len = output_buf_len
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_QUERY_DIRECTORY
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
33, # Structure size. Must be 33 as mandated by [MS-SMB2] 2.2.33
|
||||
self.info_class, # FileInformationClass
|
||||
self.flags, # Flags
|
||||
0, # FileIndex
|
||||
self.fid, # FileID
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # FileNameOffset
|
||||
len(self.filename)*2,
|
||||
self.output_buf_len) + self.filename.encode('UTF-16LE')
|
||||
|
||||
|
||||
class SMB2QueryDirectoryResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_COM_QUERY_DIRECTORY response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- data_length (integer)
|
||||
- data (string)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.34
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHI"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_QUERY_DIRECTORY
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, offset, self.data_length = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
self.data = message.raw_data[offset:offset+self.data_length]
|
||||
|
||||
|
||||
class SMB2QueryInfoRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.37
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIHHIII16s"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, flags, additional_info, info_type, file_info_class, input_buf, output_buf_len):
|
||||
self.fid = fid
|
||||
self.flags = flags
|
||||
self.additional_info = additional_info
|
||||
self.info_type = info_type
|
||||
self.file_info_class = file_info_class
|
||||
self.output_buf_len = output_buf_len
|
||||
self.input_buf = input_buf or b''
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_QUERY_INFO
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
41, # Structure size. Must be 41 as mandated by [MS-SMB2] 2.2.37
|
||||
self.info_type, # InfoType
|
||||
self.file_info_class, # FileInfoClass
|
||||
self.output_buf_len, # OutputBufferLength
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # InputBufferOffset
|
||||
0, # Reserved
|
||||
len(self.input_buf), # InputBufferLength
|
||||
self.additional_info, # AdditionalInformation
|
||||
self.flags, # Flags
|
||||
self.fid # FileId
|
||||
) + self.input_buf
|
||||
|
||||
|
||||
class SMB2QueryInfoResponse(Structure):
|
||||
"""
|
||||
Contains information about the SMB2_COM_QUERY_INFO response from the server.
|
||||
|
||||
If the message has no errors, each instance contains the following attributes:
|
||||
- data_length (integer)
|
||||
- data (string)
|
||||
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.38
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HHI"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def decode(self, message):
|
||||
assert message.command == SMB2_COM_QUERY_INFO
|
||||
|
||||
if message.status == 0:
|
||||
struct_size, buf_offset, self.data_length = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
|
||||
self.data = message.raw_data[buf_offset:buf_offset+self.data_length]
|
||||
|
||||
|
||||
class SMB2SetInfoRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.39
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = "<HBBIHHI16s"
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def __init__(self, fid, additional_info, info_type, file_info_class, data):
|
||||
self.fid = fid
|
||||
self.additional_info = additional_info
|
||||
self.info_type = info_type
|
||||
self.file_info_class = file_info_class
|
||||
self.data = data or b''
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_SET_INFO
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
33, # StructureSize. Must be 33 as mandated by [MS-SMB2] 2.2.39
|
||||
self.info_type, # InfoType
|
||||
self.file_info_class, # FileInfoClass
|
||||
len(self.data), # BufferLength
|
||||
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # BufferOffset
|
||||
0, # Reserved
|
||||
self.additional_info, # AdditionalInformation
|
||||
self.fid # FileId
|
||||
) + self.data
|
||||
|
||||
class SMB2SetInfoResponse(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.40
|
||||
"""
|
||||
|
||||
def decode(self, message):
|
||||
pass
|
||||
|
||||
|
||||
class SMB2EchoRequest(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.28
|
||||
"""
|
||||
|
||||
STRUCTURE_FORMAT = '<HH'
|
||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||
|
||||
def initMessage(self, message):
|
||||
Structure.initMessage(self, message)
|
||||
message.command = SMB2_COM_ECHO
|
||||
|
||||
def prepare(self, message):
|
||||
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||
4, # StructureSize. Must be 4 as mandated by [MS-SMB2] 2.2.29
|
||||
0) # Reserved
|
||||
|
||||
class SMB2EchoResponse(Structure):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-SMB2]: 2.2.29
|
||||
"""
|
||||
|
||||
def decode(self, message):
|
||||
pass
|
||||
257
lib/sambatools/python3/smb/smb_constants.py
Normal file
257
lib/sambatools/python3/smb/smb_constants.py
Normal file
@@ -0,0 +1,257 @@
|
||||
|
||||
# Values for Command field in SMB message header
|
||||
SMB_COM_CREATE_DIRECTORY = 0x00
|
||||
SMB_COM_DELETE_DIRECTORY = 0x01
|
||||
SMB_COM_CLOSE = 0x04
|
||||
SMB_COM_DELETE = 0x06
|
||||
SMB_COM_RENAME = 0x07
|
||||
SMB_COM_TRANSACTION = 0x25
|
||||
SMB_COM_ECHO = 0x2B
|
||||
SMB_COM_OPEN_ANDX = 0x2D
|
||||
SMB_COM_READ_ANDX = 0x2E
|
||||
SMB_COM_WRITE_ANDX = 0x2F
|
||||
SMB_COM_TRANSACTION2 = 0x32
|
||||
SMB_COM_NEGOTIATE = 0x72
|
||||
SMB_COM_SESSION_SETUP_ANDX = 0x73
|
||||
SMB_COM_TREE_CONNECT_ANDX = 0x75
|
||||
SMB_COM_NT_TRANSACT = 0xA0
|
||||
SMB_COM_NT_CREATE_ANDX = 0xA2
|
||||
|
||||
SMB_COMMAND_NAMES = {
|
||||
0x00: 'SMB_COM_CREATE_DIRECTORY',
|
||||
0x01: 'SMB_COM_DELETE_DIRECTORY',
|
||||
0x04: 'SMB_COM_CLOSE',
|
||||
0x06: 'SMB_COM_DELETE',
|
||||
0x25: 'SMB_COM_TRANSACTION',
|
||||
0x2B: 'SMB_COM_ECHO',
|
||||
0x2D: 'SMB_COM_OPEN_ANDX',
|
||||
0x2E: 'SMB_COM_READ_ANDX',
|
||||
0x2F: 'SMB_COM_WRITE_ANDX',
|
||||
0x32: 'SMB_COM_TRANSACTION2',
|
||||
0x72: 'SMB_COM_NEGOTIATE',
|
||||
0x73: 'SMB_COM_SESSION_SETUP_ANDX',
|
||||
0x75: 'SMB_COM_TREE_CONNECT_ANDX',
|
||||
0xA0: 'SMB_COM_NT_TRANSACT',
|
||||
0xA2: 'SMB_COM_NT_CREATE_ANDX',
|
||||
}
|
||||
|
||||
# Bitmask for Flags field in SMB message header
|
||||
SMB_FLAGS_LOCK_AND_READ_OK = 0x01 # LANMAN1.0
|
||||
SMB_FLAGS_BUF_AVAIL = 0x02 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CASE_INSENSITIVE = 0x08 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CANONICALIZED_PATHS = 0x10 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPLOCK = 0x20 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPBATCH = 0x40 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_REPLY = 0x80 # LANMAN1.0
|
||||
|
||||
# Bitmask for Flags2 field in SMB message header
|
||||
SMB_FLAGS2_LONG_NAMES = 0x0001 # LANMAN2.0
|
||||
SMB_FLAGS2_EAS = 0x0002 # LANMAN1.2
|
||||
SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 # NT LANMAN
|
||||
SMB_FLAGS2_IS_LONG_NAME = 0x0040 # NT LANMAN
|
||||
SMB_FLAGS2_DFS = 0x1000 # NT LANMAN
|
||||
SMB_FLAGS2_REPARSE_PATH = 0x0400 #
|
||||
SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 #
|
||||
SMB_FLAGS2_PAGING_IO = 0x2000 # NT LANMAN
|
||||
SMB_FLAGS2_NT_STATUS = 0x4000 # NT LANMAN
|
||||
SMB_FLAGS2_UNICODE = 0x8000 # NT LANMAN
|
||||
|
||||
# Bitmask for Capabilities field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
# [MS-SMB]: 2.2.4.5.2.1 (Capabilities field)
|
||||
CAP_RAW_MODE = 0x01
|
||||
CAP_MPX_MODE = 0x02
|
||||
CAP_UNICODE = 0x04
|
||||
CAP_LARGE_FILES = 0x08
|
||||
CAP_NT_SMBS = 0x10
|
||||
CAP_RPC_REMOTE_APIS = 0x20
|
||||
CAP_STATUS32 = 0x40
|
||||
CAP_LEVEL_II_OPLOCKS = 0x80
|
||||
CAP_LOCK_AND_READ = 0x0100
|
||||
CAP_NT_FIND = 0x0200
|
||||
CAP_DFS = 0x1000
|
||||
CAP_INFOLEVEL_PASSTHRU = 0x2000
|
||||
CAP_LARGE_READX = 0x4000
|
||||
CAP_LARGE_WRITEX = 0x8000
|
||||
CAP_LWIO = 0x010000
|
||||
CAP_UNIX = 0x800000
|
||||
CAP_COMPRESSED = 0x02000000
|
||||
CAP_DYNAMIC_REAUTH = 0x20000000
|
||||
CAP_PERSISTENT_HANDLES = 0x40000000
|
||||
CAP_EXTENDED_SECURITY = 0x80000000
|
||||
|
||||
# Value for Action field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
SMB_SETUP_GUEST = 0x0001
|
||||
SMB_SETUP_USE_LANMAN_KEY = 0X0002
|
||||
|
||||
# Bitmask for SecurityMode field in SMB_COM_NEGOTIATE response
|
||||
NEGOTIATE_USER_SECURITY = 0x01
|
||||
NEGOTIATE_ENCRYPT_PASSWORDS = 0x02
|
||||
NEGOTIATE_SECURITY_SIGNATURES_ENABLE = 0x04
|
||||
NEGOTIATE_SECURITY_SIGNATURES_REQUIRE = 0x08
|
||||
|
||||
# Available constants for Service field in SMB_COM_TREE_CONNECT_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.55.1 (Service field)
|
||||
SERVICE_PRINTER = 'LPT1:'
|
||||
SERVICE_NAMED_PIPE = 'IPC'
|
||||
SERVICE_COMM = 'COMM'
|
||||
SERVICE_ANY = '?????'
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
NT_CREATE_REQUEST_OPLOCK = 0x02
|
||||
NT_CREATE_REQUEST_OPBATCH = 0x04
|
||||
NT_CREATE_OPEN_TARGET_DIR = 0x08
|
||||
NT_CREATE_REQUEST_EXTENDED_RESPONSE = 0x10 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Bitmask for DesiredAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# Also used for MaximalAccess field in SMB2TreeConnectResponse class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13.1.1
|
||||
FILE_READ_DATA = 0x01
|
||||
FILE_WRITE_DATA = 0X02
|
||||
FILE_APPEND_DATA = 0x04
|
||||
FILE_READ_EA = 0x08
|
||||
FILE_WRITE_EA = 0x10
|
||||
FILE_EXECUTE = 0x20
|
||||
FILE_DELETE_CHILD = 0x40
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
FILE_WRITE_ATTRIBUTES = 0x0100
|
||||
DELETE = 0x010000
|
||||
READ_CONTROL = 0x020000
|
||||
WRITE_DAC = 0x040000
|
||||
WRITE_OWNER = 0x080000
|
||||
SYNCHRONIZE = 0x100000
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
GENERIC_ALL = 0x10000000
|
||||
GENERIC_EXECUTE = 0x20000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
GENERIC_READ = 0x80000000
|
||||
|
||||
# SMB_EXT_FILE_ATTR bitmask ([MS-CIFS]: 2.2.1.2.3)
|
||||
# Includes extensions defined in [MS-SMB] 2.2.1.2.1
|
||||
# Bitmask for FileAttributes field in SMB_COM_NT_CREATE_ANDX request ([MS-CIFS]: 2.2.4.64.1)
|
||||
# Also used for FileAttributes field in SMB2CreateRequest class ([MS-SMB2]: 2.2.13)
|
||||
ATTR_READONLY = 0x01
|
||||
ATTR_HIDDEN = 0x02
|
||||
ATTR_SYSTEM = 0x04
|
||||
ATTR_DIRECTORY = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
ATTR_NORMAL = 0x80
|
||||
ATTR_TEMPORARY = 0x0100
|
||||
ATTR_SPARSE = 0x0200
|
||||
ATTR_REPARSE_POINT = 0x0400
|
||||
ATTR_COMPRESSED = 0x0800
|
||||
ATTR_OFFLINE = 0x1000
|
||||
ATTR_NOT_CONTENT_INDEXED = 0x2000
|
||||
ATTR_ENCRYPTED = 0x4000
|
||||
POSIX_SEMANTICS = 0x01000000
|
||||
BACKUP_SEMANTICS = 0x02000000
|
||||
DELETE_ON_CLOSE = 0x04000000
|
||||
SEQUENTIAL_SCAN = 0x08000000
|
||||
RANDOM_ACCESS = 0x10000000
|
||||
NO_BUFFERING = 0x20000000
|
||||
WRITE_THROUGH = 0x80000000
|
||||
|
||||
# Bitmask for ShareAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SHARE_NONE = 0x00
|
||||
FILE_SHARE_READ = 0x01
|
||||
FILE_SHARE_WRITE = 0x02
|
||||
FILE_SHARE_DELETE = 0x04
|
||||
|
||||
# Values for CreateDisposition field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SUPERSEDE = 0x00
|
||||
FILE_OPEN = 0x01
|
||||
FILE_CREATE = 0x02
|
||||
FILE_OPEN_IF = 0x03
|
||||
FILE_OVERWRITE = 0x04
|
||||
FILE_OVERWRITE_IF = 0x05
|
||||
|
||||
# Bitmask for CreateOptions field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_DIRECTORY_FILE = 0x01
|
||||
FILE_WRITE_THROUGH = 0x02
|
||||
FILE_SEQUENTIAL_ONLY = 0x04
|
||||
FILE_NO_INTERMEDIATE_BUFFERING = 0x08
|
||||
FILE_SYNCHRONOUS_IO_ALERT = 0x10
|
||||
FILE_SYNCHRONOUS_IO_NONALERT = 0x20
|
||||
FILE_NON_DIRECTORY_FILE = 0x40
|
||||
FILE_CREATE_TREE_CONNECTION = 0x80
|
||||
FILE_COMPLETE_IF_OPLOCKED = 0x0100
|
||||
FILE_NO_EA_KNOWLEDGE = 0x0200
|
||||
FILE_OPEN_FOR_RECOVERY = 0x0400
|
||||
FILE_RANDOM_ACCESS = 0x0800
|
||||
FILE_DELETE_ON_CLOSE = 0x1000
|
||||
FILE_OPEN_BY_FILE_ID = 0x2000
|
||||
FILE_OPEN_FOR_BACKUP_INTENT = 0x4000
|
||||
FILE_NO_COMPRESSION = 0x8000
|
||||
FILE_RESERVE_OPFILTER = 0x100000
|
||||
FILE_OPEN_NO_RECALL = 0x400000
|
||||
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x800000
|
||||
|
||||
# Values for ImpersonationLevel field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# For interpretations about these values, refer to [MS-WSO] and [MSDN-IMPERS]
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
SEC_ANONYMOUS = 0x00
|
||||
SEC_IDENTIFY = 0x01
|
||||
SEC_IMPERSONATE = 0x02
|
||||
SEC_DELEGATION = 0x03 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Values for SecurityFlags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
SMB_SECURITY_CONTEXT_TRACKING = 0x01
|
||||
SMB_SECURITY_EFFECTIVE_ONLY = 0x02
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_TRANSACTION2 request
|
||||
# [MS-CIFS]: 2.2.4.46.1
|
||||
DISCONNECT_TID = 0x01
|
||||
NO_RESPONSE = 0x02
|
||||
|
||||
# Bitmask for basic file attributes
|
||||
# [MS-CIFS]: 2.2.1.2.4
|
||||
SMB_FILE_ATTRIBUTE_NORMAL = 0x00
|
||||
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
||||
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
||||
SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations
|
||||
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
||||
# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files
|
||||
# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs.
|
||||
SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000
|
||||
# Do not use the following values for listPath() operations as they are not supported for SMB2
|
||||
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
||||
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
||||
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
||||
SMB_SEARCH_ATTRIBUTE_DIRECTORY = 0x1000
|
||||
SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
|
||||
|
||||
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
||||
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
||||
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
||||
|
||||
# Bitmask for security information fields, specified as
|
||||
# AdditionalInformation in SMB2
|
||||
# [MS-SMB]: 2.2.7.4
|
||||
# [MS-SMB2]: 2.2.37
|
||||
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||
DACL_SECURITY_INFORMATION = 0x00000004
|
||||
SACL_SECURITY_INFORMATION = 0x00000008
|
||||
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||
1423
lib/sambatools/python3/smb/smb_structs.py
Normal file
1423
lib/sambatools/python3/smb/smb_structs.py
Normal file
File diff suppressed because it is too large
Load Diff
148
lib/sambatools/python3/smb/utils/U32.py
Normal file
148
lib/sambatools/python3/smb/utils/U32.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# U32.py implements 32-bit unsigned int class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
C = 0x1000000000
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def norm(n):
|
||||
return n & 0xFFFFFFFF
|
||||
|
||||
#====================================================================
|
||||
class U32:
|
||||
v = 0
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __init__(self, value = 0):
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def set(self, value = 0):
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __repr__(self):
|
||||
return hex(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __long__(self): return int(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __int__(self): return int(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __chr__(self): return chr(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __add__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v + b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __sub__(self, b):
|
||||
r = U32()
|
||||
if self.v < b.v:
|
||||
r.v = C + norm(0x100000000 - (b.v - self.v))
|
||||
else: r.v = C + norm(self.v - b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __mul__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v * b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __div__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) / norm(b.v))
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __mod__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) % norm(b.v))
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __neg__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __pos__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __abs__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __invert__(self):
|
||||
r = U32()
|
||||
r.v = C + norm(~self.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __lshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v << b)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __rshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) >> b)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __and__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v & b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __or__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v | b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __xor__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v ^ b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __not__(self):
|
||||
return U32(not norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def truth(self):
|
||||
return norm(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __cmp__(self, b):
|
||||
if norm(self.v) > norm(b.v): return 1
|
||||
elif norm(self.v) < norm(b.v): return -1
|
||||
else: return 0
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __bool__(self):
|
||||
return norm(self.v)
|
||||
|
||||
3
lib/sambatools/python3/smb/utils/__init__.py
Normal file
3
lib/sambatools/python3/smb/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
def convertFILETIMEtoEpoch(t):
|
||||
return (t - 116444736000000000) / 10000000.0;
|
||||
256
lib/sambatools/python3/smb/utils/md4.py
Normal file
256
lib/sambatools/python3/smb/utils/md4.py
Normal file
@@ -0,0 +1,256 @@
|
||||
# md4.py implements md4 hash class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# based on md4.c from "the Python Cryptography Toolkit, version 1.0.0
|
||||
# Copyright (C) 1995, A.M. Kuchling"
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
# MD4 validation data
|
||||
|
||||
md4_test= [
|
||||
('', 0x31d6cfe0d16ae931b73c59d7e0c089c0),
|
||||
("a", 0xbde52cb31de33e46245e05fbdbd6fb24),
|
||||
("abc", 0xa448017aaf21d8525fc10ae87aa6729d),
|
||||
("message digest", 0xd9130a8164549fe818874806e1c7014b),
|
||||
("abcdefghijklmnopqrstuvwxyz", 0xd79e1c308aa5bbcdeea8ed63df412da9),
|
||||
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
0x043f8582f241db351ce627e153e7f0e4),
|
||||
("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
0xe33b4ddc9c38f2199c3e7b164fcc0536),
|
||||
]
|
||||
|
||||
#====================================================================
|
||||
from .U32 import U32
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
class MD4:
|
||||
A = None
|
||||
B = None
|
||||
C = None
|
||||
D = None
|
||||
count, len1, len2 = None, None, None
|
||||
buf = []
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __init__(self):
|
||||
|
||||
|
||||
self.A = U32(0x67452301)
|
||||
self.B = U32(0xefcdab89)
|
||||
self.C = U32(0x98badcfe)
|
||||
self.D = U32(0x10325476)
|
||||
self.count, self.len1, self.len2 = U32(0), U32(0), U32(0)
|
||||
self.buf = [0x00] * 64
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __repr__(self):
|
||||
r = 'A = %s, \nB = %s, \nC = %s, \nD = %s.\n' % (self.A.__repr__(), self.B.__repr__(), self.C.__repr__(), self.D.__repr__())
|
||||
r = r + 'count = %s, \nlen1 = %s, \nlen2 = %s.\n' % (self.count.__repr__(), self.len1.__repr__(), self.len2.__repr__())
|
||||
for i in range(4):
|
||||
for j in range(16):
|
||||
r = r + '%4s ' % hex(self.buf[i+j])
|
||||
r = r + '\n'
|
||||
|
||||
return r
|
||||
#-----------------------------------------------------
|
||||
def make_copy(self):
|
||||
|
||||
dest = new()
|
||||
|
||||
dest.len1 = self.len1
|
||||
dest.len2 = self.len2
|
||||
dest.A = self.A
|
||||
dest.B = self.B
|
||||
dest.C = self.C
|
||||
dest.D = self.D
|
||||
dest.count = self.count
|
||||
for i in range(int(self.count)):
|
||||
dest.buf[i] = self.buf[i]
|
||||
|
||||
return dest
|
||||
|
||||
#-----------------------------------------------------
|
||||
def update(self, str):
|
||||
if isinstance(str, bytes):
|
||||
buf = list(str)
|
||||
else:
|
||||
buf = [ord(i) for i in str]
|
||||
|
||||
ilen = U32(len(buf))
|
||||
|
||||
# check if the first length is out of range
|
||||
# as the length is measured in bits then multiplay it by 8
|
||||
if (int(self.len1 + (ilen << 3)) < int(self.len1)):
|
||||
self.len2 = self.len2 + U32(1)
|
||||
|
||||
self.len1 = self.len1 + (ilen << 3)
|
||||
self.len2 = self.len2 + (ilen >> 29)
|
||||
|
||||
L = U32(0)
|
||||
bufpos = 0
|
||||
while (int(ilen) > 0):
|
||||
if (64 - int(self.count)) < int(ilen): L = U32(64 - int(self.count))
|
||||
else: L = ilen
|
||||
for i in range(int(L)): self.buf[i + int(self.count)] = buf[i + bufpos]
|
||||
self.count = self.count + L
|
||||
ilen = ilen - L
|
||||
bufpos = bufpos + int(L)
|
||||
|
||||
if (int(self.count) == 64):
|
||||
self.count = U32(0)
|
||||
X = []
|
||||
i = 0
|
||||
for j in range(16):
|
||||
X.append(U32(self.buf[i]) + (U32(self.buf[i+1]) << 8) + \
|
||||
(U32(self.buf[i+2]) << 16) + (U32(self.buf[i+3]) << 24))
|
||||
i = i + 4
|
||||
|
||||
A = self.A
|
||||
B = self.B
|
||||
C = self.C
|
||||
D = self.D
|
||||
|
||||
A = f1(A,B,C,D, 0, 3, X)
|
||||
D = f1(D,A,B,C, 1, 7, X)
|
||||
C = f1(C,D,A,B, 2,11, X)
|
||||
B = f1(B,C,D,A, 3,19, X)
|
||||
A = f1(A,B,C,D, 4, 3, X)
|
||||
D = f1(D,A,B,C, 5, 7, X)
|
||||
C = f1(C,D,A,B, 6,11, X)
|
||||
B = f1(B,C,D,A, 7,19, X)
|
||||
A = f1(A,B,C,D, 8, 3, X)
|
||||
D = f1(D,A,B,C, 9, 7, X)
|
||||
C = f1(C,D,A,B,10,11, X)
|
||||
B = f1(B,C,D,A,11,19, X)
|
||||
A = f1(A,B,C,D,12, 3, X)
|
||||
D = f1(D,A,B,C,13, 7, X)
|
||||
C = f1(C,D,A,B,14,11, X)
|
||||
B = f1(B,C,D,A,15,19, X)
|
||||
|
||||
A = f2(A,B,C,D, 0, 3, X)
|
||||
D = f2(D,A,B,C, 4, 5, X)
|
||||
C = f2(C,D,A,B, 8, 9, X)
|
||||
B = f2(B,C,D,A,12,13, X)
|
||||
A = f2(A,B,C,D, 1, 3, X)
|
||||
D = f2(D,A,B,C, 5, 5, X)
|
||||
C = f2(C,D,A,B, 9, 9, X)
|
||||
B = f2(B,C,D,A,13,13, X)
|
||||
A = f2(A,B,C,D, 2, 3, X)
|
||||
D = f2(D,A,B,C, 6, 5, X)
|
||||
C = f2(C,D,A,B,10, 9, X)
|
||||
B = f2(B,C,D,A,14,13, X)
|
||||
A = f2(A,B,C,D, 3, 3, X)
|
||||
D = f2(D,A,B,C, 7, 5, X)
|
||||
C = f2(C,D,A,B,11, 9, X)
|
||||
B = f2(B,C,D,A,15,13, X)
|
||||
|
||||
A = f3(A,B,C,D, 0, 3, X)
|
||||
D = f3(D,A,B,C, 8, 9, X)
|
||||
C = f3(C,D,A,B, 4,11, X)
|
||||
B = f3(B,C,D,A,12,15, X)
|
||||
A = f3(A,B,C,D, 2, 3, X)
|
||||
D = f3(D,A,B,C,10, 9, X)
|
||||
C = f3(C,D,A,B, 6,11, X)
|
||||
B = f3(B,C,D,A,14,15, X)
|
||||
A = f3(A,B,C,D, 1, 3, X)
|
||||
D = f3(D,A,B,C, 9, 9, X)
|
||||
C = f3(C,D,A,B, 5,11, X)
|
||||
B = f3(B,C,D,A,13,15, X)
|
||||
A = f3(A,B,C,D, 3, 3, X)
|
||||
D = f3(D,A,B,C,11, 9, X)
|
||||
C = f3(C,D,A,B, 7,11, X)
|
||||
B = f3(B,C,D,A,15,15, X)
|
||||
|
||||
self.A = self.A + A
|
||||
self.B = self.B + B
|
||||
self.C = self.C + C
|
||||
self.D = self.D + D
|
||||
|
||||
#-----------------------------------------------------
|
||||
def digest(self):
|
||||
|
||||
res = [0x00] * 16
|
||||
s = [0x00] * 8
|
||||
padding = [0x00] * 64
|
||||
padding[0] = 0x80
|
||||
padlen, oldlen1, oldlen2 = U32(0), U32(0), U32(0)
|
||||
|
||||
temp = self.make_copy()
|
||||
|
||||
oldlen1 = temp.len1
|
||||
oldlen2 = temp.len2
|
||||
if (56 <= int(self.count)): padlen = U32(56 - int(self.count) + 64)
|
||||
else: padlen = U32(56 - int(self.count))
|
||||
|
||||
temp.update(int_array2str(padding[:int(padlen)]))
|
||||
|
||||
s[0]= (oldlen1) & U32(0xFF)
|
||||
s[1]=((oldlen1) >> 8) & U32(0xFF)
|
||||
s[2]=((oldlen1) >> 16) & U32(0xFF)
|
||||
s[3]=((oldlen1) >> 24) & U32(0xFF)
|
||||
s[4]= (oldlen2) & U32(0xFF)
|
||||
s[5]=((oldlen2) >> 8) & U32(0xFF)
|
||||
s[6]=((oldlen2) >> 16) & U32(0xFF)
|
||||
s[7]=((oldlen2) >> 24) & U32(0xFF)
|
||||
temp.update(int_array2str(s))
|
||||
|
||||
res[ 0]= temp.A & U32(0xFF)
|
||||
res[ 1]=(temp.A >> 8) & U32(0xFF)
|
||||
res[ 2]=(temp.A >> 16) & U32(0xFF)
|
||||
res[ 3]=(temp.A >> 24) & U32(0xFF)
|
||||
res[ 4]= temp.B & U32(0xFF)
|
||||
res[ 5]=(temp.B >> 8) & U32(0xFF)
|
||||
res[ 6]=(temp.B >> 16) & U32(0xFF)
|
||||
res[ 7]=(temp.B >> 24) & U32(0xFF)
|
||||
res[ 8]= temp.C & U32(0xFF)
|
||||
res[ 9]=(temp.C >> 8) & U32(0xFF)
|
||||
res[10]=(temp.C >> 16) & U32(0xFF)
|
||||
res[11]=(temp.C >> 24) & U32(0xFF)
|
||||
res[12]= temp.D & U32(0xFF)
|
||||
res[13]=(temp.D >> 8) & U32(0xFF)
|
||||
res[14]=(temp.D >> 16) & U32(0xFF)
|
||||
res[15]=(temp.D >> 24) & U32(0xFF)
|
||||
|
||||
return int_array2str(res).encode('UTF-16LE')
|
||||
|
||||
#====================================================================
|
||||
# helpers
|
||||
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
|
||||
def G(x, y, z): return (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
|
||||
def H(x, y, z): return ((x) ^ (y) ^ (z))
|
||||
|
||||
def ROL(x, n): return (((x) << n) | ((x) >> (32-n)))
|
||||
|
||||
def f1(a, b, c, d, k, s, X): return ROL(a + F(b, c, d) + X[k], s)
|
||||
def f2(a, b, c, d, k, s, X): return ROL(a + G(b, c, d) + X[k] + U32(0x5a827999), s)
|
||||
def f3(a, b, c, d, k, s, X): return ROL(a + H(b, c, d) + X[k] + U32(0x6ed9eba1), s)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# helper function
|
||||
def int_array2str(array):
|
||||
str = ''
|
||||
for i in array:
|
||||
str = str + chr(i)
|
||||
return str
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# To be able to use md4.new() instead of md4.MD4()
|
||||
new = MD4
|
||||
852
lib/sambatools/python3/smb/utils/pyDes.py
Normal file
852
lib/sambatools/python3/smb/utils/pyDes.py
Normal file
@@ -0,0 +1,852 @@
|
||||
#############################################################################
|
||||
# Documentation #
|
||||
#############################################################################
|
||||
|
||||
# Author: Todd Whiteman
|
||||
# Date: 16th March, 2009
|
||||
# Verion: 2.0.0
|
||||
# License: Public Domain - free to do as you wish
|
||||
# Homepage: http://twhiteman.netfirms.com/des.html
|
||||
#
|
||||
# This is a pure python implementation of the DES encryption algorithm.
|
||||
# It's pure python to avoid portability issues, since most DES
|
||||
# implementations are programmed in C (for performance reasons).
|
||||
#
|
||||
# Triple DES class is also implemented, utilising the DES base. Triple DES
|
||||
# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
|
||||
#
|
||||
# See the README.txt that should come with this python module for the
|
||||
# implementation methods used.
|
||||
#
|
||||
# Thanks to:
|
||||
# * David Broadwell for ideas, comments and suggestions.
|
||||
# * Mario Wolff for pointing out and debugging some triple des CBC errors.
|
||||
# * Santiago Palladino for providing the PKCS5 padding technique.
|
||||
# * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
|
||||
#
|
||||
"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.
|
||||
|
||||
Class initialization
|
||||
--------------------
|
||||
pyDes.des(key, [mode], [IV], [pad], [padmode])
|
||||
pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
|
||||
|
||||
key -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
|
||||
for Triple DES
|
||||
mode -> Optional argument for encryption type, can be either
|
||||
pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Length must be 8 bytes.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use during
|
||||
all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
|
||||
to use during all encrypt/decrpt operations done with this instance.
|
||||
|
||||
I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
|
||||
padding issues, as the padding can be removed unambiguously upon decrypting
|
||||
data that was encrypted using PAD_PKCS5 padmode.
|
||||
|
||||
Common methods
|
||||
--------------
|
||||
encrypt(data, [pad], [padmode])
|
||||
decrypt(data, [pad], [padmode])
|
||||
|
||||
data -> Bytes to be encrypted/decrypted
|
||||
pad -> Optional argument. Only when using padmode of PAD_NORMAL. For
|
||||
encryption, adds this characters to the end of the data block when
|
||||
data is not a multiple of 8 bytes. For decryption, will remove the
|
||||
trailing characters that match this pad character from the last 8
|
||||
bytes of the unencrypted data block.
|
||||
padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
|
||||
or PAD_PKCS5). Defaults to PAD_NORMAL.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
from pyDes import *
|
||||
|
||||
data = "Please encrypt my data"
|
||||
k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
# For Python3, you'll need to use bytes, i.e.:
|
||||
# data = b"Please encrypt my data"
|
||||
# k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
d = k.encrypt(data)
|
||||
print "Encrypted: %r" % d
|
||||
print "Decrypted: %r" % k.decrypt(d)
|
||||
assert k.decrypt(d, padmode=PAD_PKCS5) == data
|
||||
|
||||
|
||||
See the module source (pyDes.py) for more examples of use.
|
||||
You can also run the pyDes.py file without and arguments to see a simple test.
|
||||
|
||||
Note: This code was not written for high-end systems needing a fast
|
||||
implementation, but rather a handy portable solution with small usage.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
# _pythonMajorVersion is used to handle Python2 and Python3 differences.
|
||||
_pythonMajorVersion = sys.version_info[0]
|
||||
|
||||
# Modes of crypting / cyphering
|
||||
ECB = 0
|
||||
CBC = 1
|
||||
|
||||
# Modes of padding
|
||||
PAD_NORMAL = 1
|
||||
PAD_PKCS5 = 2
|
||||
|
||||
# PAD_PKCS5: is a method that will unambiguously remove all padding
|
||||
# characters after decryption, when originally encrypted with
|
||||
# this padding mode.
|
||||
# For a good description of the PKCS5 padding technique, see:
|
||||
# http://www.faqs.org/rfcs/rfc1423.html
|
||||
|
||||
# The base class shared by des and triple des.
|
||||
class _baseDes(object):
|
||||
def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
if IV:
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
if pad:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self.block_size = 8
|
||||
# Sanity checking of arguments.
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if IV and len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
|
||||
# Set the passed in variables
|
||||
self._mode = mode
|
||||
self._iv = IV
|
||||
self._padding = pad
|
||||
self._padmode = padmode
|
||||
|
||||
def getKey(self):
|
||||
"""getKey() -> bytes"""
|
||||
return self.__key
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object."""
|
||||
key = self._guardAgainstUnicode(key)
|
||||
self.__key = key
|
||||
|
||||
def getMode(self):
|
||||
"""getMode() -> pyDes.ECB or pyDes.CBC"""
|
||||
return self._mode
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
self._mode = mode
|
||||
|
||||
def getPadding(self):
|
||||
"""getPadding() -> bytes of length 1. Padding character."""
|
||||
return self._padding
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self._padding = pad
|
||||
|
||||
def getPadMode(self):
|
||||
"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
return self._padmode
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
self._padmode = mode
|
||||
|
||||
def getIV(self):
|
||||
"""getIV() -> bytes"""
|
||||
return self._iv
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
if not IV or len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
self._iv = IV
|
||||
|
||||
def _padData(self, data, pad, padmode):
|
||||
# Pad data depending on the mode
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if len(data) % self.block_size == 0:
|
||||
# No padding required.
|
||||
return data
|
||||
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if not pad:
|
||||
raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
|
||||
data += (self.block_size - (len(data) % self.block_size)) * pad
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
pad_len = 8 - (len(data) % self.block_size)
|
||||
if _pythonMajorVersion < 3:
|
||||
data += pad_len * chr(pad_len)
|
||||
else:
|
||||
data += bytes([pad_len] * pad_len)
|
||||
|
||||
return data
|
||||
|
||||
def _unpadData(self, data, pad, padmode):
|
||||
# Unpad data depending on the mode.
|
||||
if not data:
|
||||
return data
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if pad:
|
||||
data = data[:-self.block_size] + \
|
||||
data[-self.block_size:].rstrip(pad)
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
if _pythonMajorVersion < 3:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
data = data[:-pad_len]
|
||||
|
||||
return data
|
||||
|
||||
def _guardAgainstUnicode(self, data):
|
||||
# Only accept byte strings or ascii unicode values, otherwise
|
||||
# there is no way to correctly decode the data into bytes.
|
||||
if _pythonMajorVersion < 3:
|
||||
if isinstance(data, str):
|
||||
raise ValueError("pyDes can only work with bytes, not Unicode strings.")
|
||||
else:
|
||||
if isinstance(data, str):
|
||||
# Only accept ascii unicode values.
|
||||
try:
|
||||
return data.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
raise ValueError("pyDes can only work with encoded strings, not Unicode.")
|
||||
return data
|
||||
|
||||
#############################################################################
|
||||
# DES #
|
||||
#############################################################################
|
||||
class des(_baseDes):
|
||||
"""DES encryption/decrytpion class
|
||||
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key,[mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be exactly 8 bytes
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrpt operations done
|
||||
with this instance.
|
||||
"""
|
||||
|
||||
|
||||
# Permutation and translation tables for DES
|
||||
__pc1 = [56, 48, 40, 32, 24, 16, 8,
|
||||
0, 57, 49, 41, 33, 25, 17,
|
||||
9, 1, 58, 50, 42, 34, 26,
|
||||
18, 10, 2, 59, 51, 43, 35,
|
||||
62, 54, 46, 38, 30, 22, 14,
|
||||
6, 61, 53, 45, 37, 29, 21,
|
||||
13, 5, 60, 52, 44, 36, 28,
|
||||
20, 12, 4, 27, 19, 11, 3
|
||||
]
|
||||
|
||||
# number left rotations of pc1
|
||||
__left_rotations = [
|
||||
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
|
||||
]
|
||||
|
||||
# permuted choice key (table 2)
|
||||
__pc2 = [
|
||||
13, 16, 10, 23, 0, 4,
|
||||
2, 27, 14, 5, 20, 9,
|
||||
22, 18, 11, 3, 25, 7,
|
||||
15, 6, 26, 19, 12, 1,
|
||||
40, 51, 30, 36, 46, 54,
|
||||
29, 39, 50, 44, 32, 47,
|
||||
43, 48, 38, 55, 33, 52,
|
||||
45, 41, 49, 35, 28, 31
|
||||
]
|
||||
|
||||
# initial permutation IP
|
||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7,
|
||||
56, 48, 40, 32, 24, 16, 8, 0,
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6
|
||||
]
|
||||
|
||||
# Expansion table for turning 32 bit blocks into 48 bits
|
||||
__expansion_table = [
|
||||
31, 0, 1, 2, 3, 4,
|
||||
3, 4, 5, 6, 7, 8,
|
||||
7, 8, 9, 10, 11, 12,
|
||||
11, 12, 13, 14, 15, 16,
|
||||
15, 16, 17, 18, 19, 20,
|
||||
19, 20, 21, 22, 23, 24,
|
||||
23, 24, 25, 26, 27, 28,
|
||||
27, 28, 29, 30, 31, 0
|
||||
]
|
||||
|
||||
# The (in)famous S-boxes
|
||||
__sbox = [
|
||||
# S1
|
||||
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
||||
|
||||
# S2
|
||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
||||
|
||||
# S3
|
||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
||||
|
||||
# S4
|
||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
||||
|
||||
# S5
|
||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
||||
|
||||
# S6
|
||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
||||
|
||||
# S7
|
||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
||||
|
||||
# S8
|
||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
|
||||
]
|
||||
|
||||
|
||||
# 32-bit permutation function P used on the output of the S-boxes
|
||||
__p = [
|
||||
15, 6, 19, 20, 28, 11,
|
||||
27, 16, 0, 14, 22, 25,
|
||||
4, 17, 30, 9, 1, 7,
|
||||
23,13, 31, 26, 2, 8,
|
||||
18, 12, 29, 5, 21, 10,
|
||||
3, 24
|
||||
]
|
||||
|
||||
# final permutation IP^-1
|
||||
__fp = [
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25,
|
||||
32, 0, 40, 8, 48, 16, 56, 24
|
||||
]
|
||||
|
||||
# Type of crypting being done
|
||||
ENCRYPT = 0x00
|
||||
DECRYPT = 0x01
|
||||
|
||||
# Initialisation
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
# Sanity checking of arguments.
|
||||
if len(key) != 8:
|
||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.key_size = 8
|
||||
|
||||
self.L = []
|
||||
self.R = []
|
||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
||||
self.final = []
|
||||
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Must be 8 bytes."""
|
||||
_baseDes.setKey(self, key)
|
||||
self.__create_sub_keys()
|
||||
|
||||
def __String_to_BitList(self, data):
|
||||
"""Turn the string data, into a list of bits (1, 0)'s"""
|
||||
if _pythonMajorVersion < 3:
|
||||
# Turn the strings into integers. Python 3 uses a bytes
|
||||
# class, which already has this behaviour.
|
||||
data = [ord(c) for c in data]
|
||||
l = len(data) * 8
|
||||
result = [0] * l
|
||||
pos = 0
|
||||
for ch in data:
|
||||
i = 7
|
||||
while i >= 0:
|
||||
if ch & (1 << i) != 0:
|
||||
result[pos] = 1
|
||||
else:
|
||||
result[pos] = 0
|
||||
pos += 1
|
||||
i -= 1
|
||||
|
||||
return result
|
||||
|
||||
def __BitList_to_String(self, data):
|
||||
"""Turn the list of bits -> data, into a string"""
|
||||
result = []
|
||||
pos = 0
|
||||
c = 0
|
||||
while pos < len(data):
|
||||
c += data[pos] << (7 - (pos % 8))
|
||||
if (pos % 8) == 7:
|
||||
result.append(c)
|
||||
c = 0
|
||||
pos += 1
|
||||
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join([ chr(c) for c in result ])
|
||||
else:
|
||||
return bytes(result)
|
||||
|
||||
def __permutate(self, table, block):
|
||||
"""Permutate this block with the specified table"""
|
||||
return list([block[x] for x in table])
|
||||
|
||||
# Transform the secret key, so that it is ready for data processing
|
||||
# Create the 16 subkeys, K[1] - K[16]
|
||||
def __create_sub_keys(self):
|
||||
"""Create the 16 subkeys K[1] to K[16] from the given key"""
|
||||
key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
|
||||
i = 0
|
||||
# Split into Left and Right sections
|
||||
self.L = key[:28]
|
||||
self.R = key[28:]
|
||||
while i < 16:
|
||||
j = 0
|
||||
# Perform circular left shifts
|
||||
while j < des.__left_rotations[i]:
|
||||
self.L.append(self.L[0])
|
||||
del self.L[0]
|
||||
|
||||
self.R.append(self.R[0])
|
||||
del self.R[0]
|
||||
|
||||
j += 1
|
||||
|
||||
# Create one of the 16 subkeys through pc2 permutation
|
||||
self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
|
||||
|
||||
i += 1
|
||||
|
||||
# Main part of the encryption algorithm, the number cruncher :)
|
||||
def __des_crypt(self, block, crypt_type):
|
||||
"""Crypt the block of data through DES bit-manipulation"""
|
||||
block = self.__permutate(des.__ip, block)
|
||||
self.L = block[:32]
|
||||
self.R = block[32:]
|
||||
|
||||
# Encryption starts from Kn[1] through to Kn[16]
|
||||
if crypt_type == des.ENCRYPT:
|
||||
iteration = 0
|
||||
iteration_adjustment = 1
|
||||
# Decryption starts from Kn[16] down to Kn[1]
|
||||
else:
|
||||
iteration = 15
|
||||
iteration_adjustment = -1
|
||||
|
||||
i = 0
|
||||
while i < 16:
|
||||
# Make a copy of R[i-1], this will later become L[i]
|
||||
tempR = self.R[:]
|
||||
|
||||
# Permutate R[i - 1] to start creating R[i]
|
||||
self.R = self.__permutate(des.__expansion_table, self.R)
|
||||
|
||||
# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
|
||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
||||
# Optimization: Replaced below commented code with above
|
||||
#j = 0
|
||||
#B = []
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.Kn[iteration][j]
|
||||
# j += 1
|
||||
# if j % 6 == 0:
|
||||
# B.append(self.R[j-6:j])
|
||||
|
||||
# Permutate B[1] to B[8] using the S-Boxes
|
||||
j = 0
|
||||
Bn = [0] * 32
|
||||
pos = 0
|
||||
while j < 8:
|
||||
# Work out the offsets
|
||||
m = (B[j][0] << 1) + B[j][5]
|
||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
||||
|
||||
# Find the permutation value
|
||||
v = des.__sbox[j][(m << 4) + n]
|
||||
|
||||
# Turn value into bits, add it to result: Bn
|
||||
Bn[pos] = (v & 8) >> 3
|
||||
Bn[pos + 1] = (v & 4) >> 2
|
||||
Bn[pos + 2] = (v & 2) >> 1
|
||||
Bn[pos + 3] = v & 1
|
||||
|
||||
pos += 4
|
||||
j += 1
|
||||
|
||||
# Permutate the concatination of B[1] to B[8] (Bn)
|
||||
self.R = self.__permutate(des.__p, Bn)
|
||||
|
||||
# Xor with L[i - 1]
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
|
||||
# Optimization: This now replaces the below commented code
|
||||
#j = 0
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.L[j]
|
||||
# j += 1
|
||||
|
||||
# L[i] becomes R[i - 1]
|
||||
self.L = tempR
|
||||
|
||||
i += 1
|
||||
iteration += iteration_adjustment
|
||||
|
||||
# Final permutation of R[16]L[16]
|
||||
self.final = self.__permutate(des.__fp, self.R + self.L)
|
||||
return self.final
|
||||
|
||||
|
||||
# Data to be encrypted/decrypted
|
||||
def crypt(self, data, crypt_type):
|
||||
"""Crypt the data in blocks, running it through des_crypt()"""
|
||||
|
||||
# Error check the data
|
||||
if not data:
|
||||
return ''
|
||||
if len(data) % self.block_size != 0:
|
||||
if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
||||
if not self.getPadding():
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
||||
else:
|
||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
||||
# print "Len of data: %f" % (len(data) / self.block_size)
|
||||
|
||||
if self.getMode() == CBC:
|
||||
if self.getIV():
|
||||
iv = self.__String_to_BitList(self.getIV())
|
||||
else:
|
||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
||||
|
||||
# Split the data into blocks, crypting each one seperately
|
||||
i = 0
|
||||
dict = {}
|
||||
result = []
|
||||
#cached = 0
|
||||
#lines = 0
|
||||
while i < len(data):
|
||||
# Test code for caching encryption results
|
||||
#lines += 1
|
||||
#if dict.has_key(data[i:i+8]):
|
||||
#print "Cached result for: %s" % data[i:i+8]
|
||||
# cached += 1
|
||||
# result.append(dict[data[i:i+8]])
|
||||
# i += 8
|
||||
# continue
|
||||
|
||||
block = self.__String_to_BitList(data[i:i+8])
|
||||
|
||||
# Xor with IV if using CBC mode
|
||||
if self.getMode() == CBC:
|
||||
if crypt_type == des.ENCRYPT:
|
||||
block = list(map(lambda x, y: x ^ y, block, iv))
|
||||
#j = 0
|
||||
#while j < len(block):
|
||||
# block[j] = block[j] ^ iv[j]
|
||||
# j += 1
|
||||
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
if crypt_type == des.DECRYPT:
|
||||
processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
|
||||
#j = 0
|
||||
#while j < len(processed_block):
|
||||
# processed_block[j] = processed_block[j] ^ iv[j]
|
||||
# j += 1
|
||||
iv = block
|
||||
else:
|
||||
iv = processed_block
|
||||
else:
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
|
||||
# Add the resulting crypted block to our list
|
||||
#d = self.__BitList_to_String(processed_block)
|
||||
#result.append(d)
|
||||
result.append(self.__BitList_to_String(processed_block))
|
||||
#dict[data[i:i+8]] = d
|
||||
i += 8
|
||||
|
||||
# print "Lines: %d, cached: %d" % (lines, cached)
|
||||
|
||||
# Return the full crypted string
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self._padData(data, pad, padmode)
|
||||
return self.crypt(data, des.ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be encrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after decrypting.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self.crypt(data, des.DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
||||
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Triple DES #
|
||||
#############################################################################
|
||||
class triple_des(_baseDes):
|
||||
"""Triple DES encryption/decrytpion class
|
||||
|
||||
This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
|
||||
the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key, [mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be either 16 or
|
||||
24 bytes long
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrpt operations done
|
||||
with this instance.
|
||||
"""
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
|
||||
self.key_size = 24 # Use DES-EDE3 mode
|
||||
if len(key) != self.key_size:
|
||||
if len(key) == 16: # Use DES-EDE2 mode
|
||||
self.key_size = 16
|
||||
else:
|
||||
raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
|
||||
if self.getMode() == CBC:
|
||||
if not self.getIV():
|
||||
# Use the first 8 bytes of the key
|
||||
self._iv = key[:self.block_size]
|
||||
if len(self.getIV()) != self.block_size:
|
||||
raise ValueError("Invalid IV, must be 8 bytes in length")
|
||||
self.__key1 = des(key[:8], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
self.__key2 = des(key[8:16], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
if self.key_size == 16:
|
||||
self.__key3 = self.__key1
|
||||
else:
|
||||
self.__key3 = des(key[16:], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
_baseDes.setKey(self, key)
|
||||
|
||||
# Override setter methods to work on all 3 keys.
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
_baseDes.setMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setMode(mode)
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
_baseDes.setPadding(self, pad)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadding(pad)
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
_baseDes.setPadMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadMode(mode)
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
_baseDes.setIV(self, IV)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setIV(IV)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
# Pad the data accordingly.
|
||||
data = self._padData(data, pad, padmode)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
block = self.__key1.crypt(data[i:i+8], ENCRYPT)
|
||||
block = self.__key2.crypt(block, DECRYPT)
|
||||
block = self.__key3.crypt(block, ENCRYPT)
|
||||
self.__key1.setIV(block)
|
||||
self.__key2.setIV(block)
|
||||
self.__key3.setIV(block)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key1.crypt(data, ENCRYPT)
|
||||
data = self.__key2.crypt(data, DECRYPT)
|
||||
return self.__key3.crypt(data, ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after
|
||||
decrypting, no pad character is required for PAD_PKCS5.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
iv = data[i:i+8]
|
||||
block = self.__key3.crypt(iv, DECRYPT)
|
||||
block = self.__key2.crypt(block, ENCRYPT)
|
||||
block = self.__key1.crypt(block, DECRYPT)
|
||||
self.__key1.setIV(iv)
|
||||
self.__key2.setIV(iv)
|
||||
self.__key3.setIV(iv)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
data = ''.join(result)
|
||||
else:
|
||||
data = bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key3.crypt(data, DECRYPT)
|
||||
data = self.__key2.crypt(data, ENCRYPT)
|
||||
data = self.__key1.crypt(data, DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
||||
22
lib/sambatools/python3/smb/utils/rc4.py
Normal file
22
lib/sambatools/python3/smb/utils/rc4.py
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
def RC4_encrypt(key, data):
|
||||
S = list(range(256))
|
||||
j = 0
|
||||
|
||||
key_len = len(key)
|
||||
for i in list(range(256)):
|
||||
j = (j + S[i] + key[i % key_len]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
|
||||
j = 0
|
||||
y = 0
|
||||
out = []
|
||||
|
||||
for char in data:
|
||||
j = (j + 1) % 256
|
||||
y = (y + S[j]) % 256
|
||||
S[j], S[y] = S[y], S[j]
|
||||
|
||||
out.append(char ^ S[(S[j] + S[y]) % 256])
|
||||
|
||||
return bytes(out)
|
||||
109
lib/sambatools/python3/smb/utils/sha256.py
Normal file
109
lib/sambatools/python3/smb/utils/sha256.py
Normal file
@@ -0,0 +1,109 @@
|
||||
__author__ = 'Thomas Dixon'
|
||||
__license__ = 'MIT'
|
||||
|
||||
import copy, struct, sys
|
||||
|
||||
digest_size = 32
|
||||
blocksize = 1
|
||||
|
||||
def new(m=None):
|
||||
return sha256(m)
|
||||
|
||||
class sha256(object):
|
||||
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
|
||||
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
|
||||
_output_size = 8
|
||||
|
||||
blocksize = 1
|
||||
block_size = 64
|
||||
digest_size = 32
|
||||
|
||||
def __init__(self, m=None):
|
||||
self._buffer = ''
|
||||
self._counter = 0
|
||||
|
||||
if m is not None:
|
||||
if type(m) is not str:
|
||||
raise TypeError('%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__))
|
||||
self.update(m)
|
||||
|
||||
def _rotr(self, x, y):
|
||||
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
|
||||
|
||||
def _sha256_process(self, c):
|
||||
w = [0]*64
|
||||
w[0:15] = struct.unpack('!16L', c)
|
||||
|
||||
for i in range(16, 64):
|
||||
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
|
||||
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
|
||||
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
|
||||
|
||||
a,b,c,d,e,f,g,h = self._h
|
||||
|
||||
for i in range(64):
|
||||
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
|
||||
maj = (a & b) ^ (a & c) ^ (b & c)
|
||||
t2 = s0 + maj
|
||||
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
|
||||
ch = (e & f) ^ ((~e) & g)
|
||||
t1 = h + s1 + ch + self._k[i] + w[i]
|
||||
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = (d + t1) & 0xFFFFFFFF
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = (t1 + t2) & 0xFFFFFFFF
|
||||
|
||||
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
|
||||
|
||||
def update(self, m):
|
||||
if not m:
|
||||
return
|
||||
if type(m) is not str:
|
||||
raise TypeError('%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__))
|
||||
|
||||
self._buffer += m
|
||||
self._counter += len(m)
|
||||
|
||||
while len(self._buffer) >= 64:
|
||||
self._sha256_process(self._buffer[:64])
|
||||
self._buffer = self._buffer[64:]
|
||||
|
||||
def digest(self):
|
||||
mdi = self._counter & 0x3F
|
||||
length = struct.pack('!Q', self._counter<<3)
|
||||
|
||||
if mdi < 56:
|
||||
padlen = 55-mdi
|
||||
else:
|
||||
padlen = 119-mdi
|
||||
|
||||
r = self.copy()
|
||||
r.update('\x80'+('\x00'*padlen)+length)
|
||||
return ''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
|
||||
|
||||
def hexdigest(self):
|
||||
return self.digest().encode('hex')
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
Reference in New Issue
Block a user