retrieveFileFromOffset() (smb.SMBConnection.SMBConnection method)

retrieveFileFromOffset() (smb.SMBProtocol.SMBProtocolFactory method)

SIGN_WHEN_REQUIRED (smb.SMBProtocol.SMBProtocolFactory attribute)

SIGN_WHEN_SUPPORTED (smb.SMBProtocol.SMBProtocolFactory attribute)

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 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*.
        """
                break
            else:

        data = self.prepareNetNameQuery(trn_id)

        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)

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')

        """
        Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.

        """
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            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

        assert self.sock, 'Socket is already closed'
        self.sock.sendto(data, ( ip, port ))

        """

        :return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
        """

        return self._pollForNetBIOSPacket(trn_id, timeout)

        """

        :return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
        """

        data = self.prepareNetNameQuery(trn_id, False)
        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
            try:
                _timeout = time.time()-end_time
                if _timeout <= 0:
                    return None

                ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
                if not ready:

                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):
            try:

                    return ret
                        raise ex
                else:
                    raise ex

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.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 = None, 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.

        :param port: Defaults to 139. If you are using direct TCP mode (is_direct_tcp=true when creating this SMBConnection instance), use 445.
        :param sock_family: In Python 3.x, use *None* as we can infer the socket family from the provided *ip*. In Python 2.x, it must be either *socket.AF_INET* or *socket.AF_INET6*.
        :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()

        if sock_family:
            self.sock = socket.socket(sock_family)
            self.sock.settimeout(timeout)
            self.sock.connect(( ip, port ))
        else:
            self.sock = socket.create_connection(( 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

        """
        Terminate the SMB connection (if it has been started) and release any sources held by the underlying socket.
        """
            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):
            results.extend(entries)

        def eb(failure):
            raise failure

        try:
            self._listShares(cb, eb, timeout)
            while self.is_busy:
        finally:

        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.
        """

        try:
            self._listPath(service_name, path, cb, eb, search = search, pattern = pattern, timeout = timeout)
        finally:

    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 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
        """

        try:
            self._listSnapshots(service_name, path, cb, eb, timeout = timeout)
        finally:

    def getAttributes(self, service_name, path, timeout = 30):
        """
        Retrieve information about the file at *path* on the *service_name*.

        :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.
        """

        def cb(info):
            results.append(info)

        try:
            self._getAttributes(service_name, path, cb, eb, timeout)
        finally:

        return results[0]

    def getSecurity(self, service_name, path, timeout = 30):
        """
        Retrieve the security descriptor of the file at *path* on the *service_name*.

        :return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file.
        """

        try:
            self._getSecurity(service_name, path, cb, eb, timeout)
        finally:

    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 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):
        """

        :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.
        """

        def cb(r):
            results.append(r[1:])

        try:
            self._retrieveFileFromOffset(service_name, path, file_obj, cb, eb, offset, max_length, timeout = timeout)
        finally:

    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 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):
        """

        :param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created.
        :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.
        """

        def cb(r):
            results.append(r[1])

        try:
            self._storeFileFromOffset(service_name, path, file_obj, cb, eb, offset, truncate = truncate, timeout = timeout)
        finally:

    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.
        """

        def cb(r):

        try:
            self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout)
        finally:

    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!

                                                 Wildcards may be used in the filename component of the path.
        :param int file_attributes: The desired file attributes to set. Defaults to `ATTR_NORMAL`.
        """

        def cb(r):

        try:
            self._resetFileAttributes(service_name, path_file_pattern, cb, eb, file_attributes, timeout)
        finally:

    def createDirectory(self, service_name, path, timeout = 30):
        """
        Creates a new directory *path* on the *service_name*.

        :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.
        """

        def cb(r):

        try:
            self._createDirectory(service_name, path, cb, eb, timeout = timeout)
        finally:

    def deleteDirectory(self, service_name, path, timeout = 30):
        """
        Delete the empty folder at *path* on *service_name*

        :param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
        """

        def cb(r):

        try:
            self._deleteDirectory(service_name, path, cb, eb, timeout = timeout)
        finally:

    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.

        """

        def cb(r):

        try:
            self._rename(service_name, old_path, new_path, cb, eb)
        finally:

    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
        """

        def cb(r):
            results.append(r)

        try:
            self._echo(data, cb, eb)
        finally:

    #
    #

    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)

                d = self.sock.recv(read_len)
                if len(d) == 0:

                data = data + d
                read_len -= len(d)
                if isinstance(ex, tuple):
                        raise ex
                else:
                    raise ex

        type, flags, length = struct.unpack('>BBH', data)
        if flags & 0x01:
            length = length | 0x10000

        read_len = length
            try:

                        raise ex
                else:
                    raise ex

        self.feedData(data)

import os, logging, time
from twisted.internet.protocol import ClientFactory, Protocol

__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.transport.write(data)

            self.factory.onAuthOK()
            reactor.callLater(1, self._cleanupPendingRequests)

            self.factory.onAuthFailed()

    def onNMBSessionFailed(self):
        self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')

    #
    #

    def _cleanupPendingRequests(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]

class SMBProtocolFactory(ClientFactory):

    protocol = SMBProtocol
    log = logging.getLogger('SMB.SMBFactory')

        """
        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.

                               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 ``\/:*?";|+``
        """
        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.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
        return self.instance and self.instance.is_using_smb2

    #
    # Public Methods for Callbacks
    #

        """
        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

        """

        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

    #
    #

        """

        :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:

        self.instance._listShares(d.callback, d.errback, timeout)
        return d

        """

        :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
        """

        self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
        return d

        """
        Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.

        :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
        """

        self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
        return d

        """

        :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.
        """

        self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
        return d

        """

        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 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.
        :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* ).
        """

        """

                                        If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
        """

        self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
        return d

        """

        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.

                                    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.
        :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 ).
        """

        self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
        return d

        """

        :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
        """

        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):
        """

        :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
        """

        self.instance._createDirectory(service_name, path, d.callback, d.errback)
        return d

    def deleteDirectory(self, service_name, path):
        """

        """

        self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
        return d

    def rename(self, service_name, old_path, new_path):
        """

        :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
        """

        self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
        return d

        """

        :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
        """

        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.

        """

        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

import logging, binascii, time, hmac
from datetime import datetime
from .smb2_constants import *
from .smb2_structs import *
from .security_descriptors import SecurityDescriptor
from nmb.base import NMBSession
from .utils import convertFILETIMEtoEpoch
from . import ntlm, securityblob

try:
    import hashlib
    sha256 = hashlib.sha256
except ImportError:
    from .utils import sha256

class NotReadyError(Exception):
    """Raised when SMB connection is not ready (i.e. not authenticated or authentication failed)"""
    pass

class NotConnectedError(Exception):
    """Raised when underlying SMB connection has been disconnected or not connected yet"""
    pass

class SMBTimeout(Exception):
    """Raised when a timeout has occurred while waiting for a response or for a SMB/CIFS operation to complete."""
    pass

class SMB(NMBSession):
    """
    This class represents a "connection" to the remote SMB/CIFS server.
    It is not meant to be used directly in an application as it does not have any network transport implementations.

    For application use, please refer to
      - L{SMBProtocol.SMBProtocolFactory<smb.SMBProtocol>} if you are using Twisted framework

    In [MS-CIFS], this class will contain attributes of Client, Client.Connection and Client.Session abstract data models.

    References:
    ===========
      - [MS-CIFS]: 3.2.1
    """

    log = logging.getLogger('SMB.SMB')

        NMBSession.__init__(self, my_name, remote_name, is_direct_tcp = is_direct_tcp)
        self._password = password
        self.use_ntlm_v2 = use_ntlm_v2 #: Similar to LMAuthenticationPolicy and NTAuthenticationPolicy as described in [MS-CIFS] 3.2.1.1
        self.smb_message = SMBMessage()
        self.is_using_smb2 = False   #: Are we communicating using SMB2 protocol? self.smb_message will be a SMB2Message instance if this flag is True
        self.async_requests = { }    #: AsyncID mapped to _PendingRequest instance
        self.pending_requests = { }  #: MID mapped to _PendingRequest instance
        self.connected_trees = { }   #: Share name mapped to TID
        self.next_rpc_call_id = 1    #: Next RPC callID value. Not used directly in SMB message. Usually encapsulated in sub-commands under SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2 messages

        self.has_negotiated = False
        self.has_authenticated = False
        self.is_signing_active = False           #: True if the remote server accepts message signing. All outgoing messages will be signed. Simiar to IsSigningActive as described in [MS-CIFS] 3.2.1.2
        self.signing_session_key = None          #: Session key for signing packets, if signing is active. Similar to SigningSessionKey as described in [MS-CIFS] 3.2.1.2
        self.signing_challenge_response = None   #: Contains the challenge response for signing, if signing is active. Similar to SigningChallengeResponse as described in [MS-CIFS] 3.2.1.2
        self.mid = 0
        self.uid = 0
        self.next_signing_id = 2     #: Similar to ClientNextSendSequenceNumber as described in [MS-CIFS] 3.2.1.2

        # SMB1 and SMB2 attributes
        # Note that the interpretations of the values may differ between SMB1 and SMB2 protocols
        self.capabilities = 0
        self.security_mode = 0     #: Initialized from the SecurityMode field of the SMB_COM_NEGOTIATE message

        # SMB1 attributes
        # Most of the following attributes will be initialized upon receipt of SMB_COM_NEGOTIATE message from server (via self._updateServerInfo_SMB1 method)
        self.use_plaintext_authentication = False  #: Similar to PlaintextAuthenticationPolicy in in [MS-CIFS] 3.2.1.1
        self.max_raw_size = 0
        self.max_buffer_size = 0   #: Similar to MaxBufferSize as described in [MS-CIFS] 3.2.1.1
        self.max_mpx_count = 0     #: Similar to MaxMpxCount as described in [MS-CIFS] 3.2.1.1

        # SMB2 attributes
        self.max_read_size = 0      #: Similar to MaxReadSize as described in [MS-SMB2] 2.2.4
        self.max_write_size = 0     #: Similar to MaxWriteSize as described in [MS-SMB2] 2.2.4
        self.max_transact_size = 0  #: Similar to MaxTransactSize as described in [MS-SMB2] 2.2.4
        self.session_id = 0         #: Similar to SessionID as described in [MS-SMB2] 2.2.4. This will be set in _updateState_SMB2 method

        self._setupSMB1Methods()

        self.log.info('Authentication with remote machine "%s" for user "%s" will be using NTLM %s authentication (%s extended security)',
                      self.remote_name, self.username,
                      (self.use_ntlm_v2 and 'v2') or 'v1',
                      (SUPPORT_EXTENDED_SECURITY and 'with') or 'without')

    @property
    def password(self):
        if callable(self._password):
            return self._password()
        return self._password

    #
    # NMBSession Methods
    #

    def onNMBSessionOK(self):
        self._sendSMBMessage(SMBMessage(ComNegotiateRequest()))

        pass

    def onNMBSessionMessage(self, flags, data):
            try:
                i = self.smb_message.decode(data)
            except SMB2ProtocolHeaderError:
                self.log.info('Now switching over to SMB2 protocol communication')
                self.is_using_smb2 = True
                self.mid = 0  # Must reset messageID counter, or else remote SMB2 server will disconnect
                self._setupSMB2Methods()
                self.smb_message = self._klassSMBMessage()

            next_message_offset = 0
            if self.is_using_smb2:
                next_message_offset = self.smb_message.next_command_offset

            if i > 0:
                if not self.is_using_smb2:
                    self.log.debug('Received SMB message "%s" (command:0x%2X flags:0x%02X flags2:0x%04X TID:%d UID:%d)',
                                   SMB_COMMAND_NAMES.get(self.smb_message.command, '<unknown>'),
                                   self.smb_message.command, self.smb_message.flags, self.smb_message.flags2, self.smb_message.tid, self.smb_message.uid)
                else:
                    self.log.debug('Received SMB2 message "%s" (command:0x%04X flags:0x%04x)',
                                   SMB2_COMMAND_NAMES.get(self.smb_message.command, '<unknown>'),
                                   self.smb_message.command, self.smb_message.flags)
                if self._updateState(self.smb_message):
                    # We need to create a new instance instead of calling reset() because the instance could be captured in the message history.
                    self.smb_message = self._klassSMBMessage()

            if next_message_offset > 0:
                data = data[next_message_offset:]
            else:
                break

    #
    # Public Methods for Overriding in Subclasses
    #

        pass

        pass

    #
    #

    def _setupSMB1Methods(self):
        self._klassSMBMessage = SMBMessage
        self._updateState = self._updateState_SMB1
        self._updateServerInfo = self._updateServerInfo_SMB1
        self._handleNegotiateResponse = self._handleNegotiateResponse_SMB1
        self._sendSMBMessage = self._sendSMBMessage_SMB1
        self._handleSessionChallenge = self._handleSessionChallenge_SMB1
        self._listShares = self._listShares_SMB1
        self._listPath = self._listPath_SMB1
        self._listSnapshots = self._listSnapshots_SMB1
        self._getSecurity = self._getSecurity_SMB1
        self._getAttributes = self._getAttributes_SMB1
        self._retrieveFile = self._retrieveFile_SMB1
        self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1
        self._storeFile = self._storeFile_SMB1
        self._storeFileFromOffset = self._storeFileFromOffset_SMB1
        self._deleteFiles = self._deleteFiles_SMB1
        self._resetFileAttributes = self._resetFileAttributes_SMB1
        self._createDirectory = self._createDirectory_SMB1
        self._deleteDirectory = self._deleteDirectory_SMB1
        self._rename = self._rename_SMB1
        self._echo = self._echo_SMB1

    def _setupSMB2Methods(self):
        self._klassSMBMessage = SMB2Message
        self._updateState = self._updateState_SMB2
        self._updateServerInfo = self._updateServerInfo_SMB2
        self._handleNegotiateResponse = self._handleNegotiateResponse_SMB2
        self._sendSMBMessage = self._sendSMBMessage_SMB2
        self._handleSessionChallenge = self._handleSessionChallenge_SMB2
        self._listShares = self._listShares_SMB2
        self._listPath = self._listPath_SMB2
        self._listSnapshots = self._listSnapshots_SMB2
        self._getSecurity = self._getSecurity_SMB2
        self._getAttributes = self._getAttributes_SMB2
        self._retrieveFile = self._retrieveFile_SMB2
        self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB2
        self._storeFile = self._storeFile_SMB2
        self._storeFileFromOffset = self._storeFileFromOffset_SMB2
        self._deleteFiles = self._deleteFiles_SMB2
        self._resetFileAttributes = self._resetFileAttributes_SMB2
        self._createDirectory = self._createDirectory_SMB2
        self._deleteDirectory = self._deleteDirectory_SMB2
        self._rename = self._rename_SMB2
        self._echo = self._echo_SMB2

    def _getNextRPCCallID(self):
        self.next_rpc_call_id += 1
        return self.next_rpc_call_id

    #
    # SMB2 Methods Family
    #

    def _sendSMBMessage_SMB2(self, smb_message):
        if smb_message.mid == 0:
            smb_message.mid = self._getNextMID_SMB2()

        if smb_message.command != SMB2_COM_NEGOTIATE:
            smb_message.session_id = self.session_id

        if self.is_signing_active:
            smb_message.flags |= SMB2_FLAGS_SIGNED
            raw_data = smb_message.encode()
            smb_message.signature = hmac.new(self.signing_session_key, raw_data, sha256).digest()[:16]

            smb_message.raw_data = smb_message.encode()
            self.log.debug('MID is %d. Signature is %s. Total raw message is %d bytes', smb_message.mid, binascii.hexlify(smb_message.signature), len(smb_message.raw_data))
        else:
        self.sendNMBMessage(smb_message.raw_data)

    def _getNextMID_SMB2(self):
        self.mid += 1
        return self.mid

    def _updateState_SMB2(self, message):
        if message.isReply:
            if message.command == SMB2_COM_NEGOTIATE:
                if message.status == 0:
                    self.has_negotiated = True
                    self.log.info('SMB2 dialect negotiation successful')
                    self._updateServerInfo(message.payload)
                    self._handleNegotiateResponse(message)
                else:
                    raise ProtocolError('Unknown status value (0x%08X) in SMB2_COM_NEGOTIATE' % message.status,
                                        message.raw_data, message)
            elif message.command == SMB2_COM_SESSION_SETUP:
                    self.session_id = message.session_id
                    try:
                        result = securityblob.decodeAuthResponseSecurityBlob(message.payload.security_blob)
                        if result == securityblob.RESULT_ACCEPT_COMPLETED:
                            self.has_authenticated = True
                            self.log.info('Authentication (on SMB2) successful!')

                            # [MS-SMB2]: 3.2.5.3.1
                            # If the security subsystem indicates that the session was established by an anonymous user,
                            # Session.SigningRequired MUST be set to FALSE.
                            # If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the
                            # SMB2 SESSION_SETUP Response and if Session.SigningRequired is TRUE, this indicates a SESSION_SETUP
                            # failure and the connection MUST be terminated. If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags
                            # field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired
                            # MUST be set to FALSE.
                            if message.payload.isGuestSession or message.payload.isAnonymousSession:
                                self.is_signing_active = False
                                self.log.info('Signing disabled because session is guest/anonymous')

                            self.onAuthOK()
                        else:
                            raise ProtocolError('SMB2_COM_SESSION_SETUP status is 0 but security blob negResult value is %d' % result, message.raw_data, message)
                    except securityblob.BadSecurityBlobError as ex:
                        raise ProtocolError(str(ex), message.raw_data, message)
                elif message.status == 0xc0000016:  # STATUS_MORE_PROCESSING_REQUIRED
                    try:
                        result, ntlm_token = securityblob.decodeChallengeSecurityBlob(message.payload.security_blob)
                        if result == securityblob.RESULT_ACCEPT_INCOMPLETE:
                            self._handleSessionChallenge(message, ntlm_token)
                    except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ) as ex:
                elif (message.status == 0xc000006d   # STATUS_LOGON_FAILURE
                    or message.status == 0xc0000064  # STATUS_NO_SUCH_USER
                    or message.status == 0xc000006a):# STATUS_WRONG_PASSWORD
                    self.has_authenticated = False
                    self.log.info('Authentication (on SMB2) failed. Please check username and password.')
                    self.onAuthFailed()
                elif (message.status == 0xc0000193    # STATUS_ACCOUNT_EXPIRED
                    or message.status == 0xC0000071): # STATUS_PASSWORD_EXPIRED
                    self.log.info('Authentication (on SMB2) failed. Account or password has expired.')
                elif message.status == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT
                    self.log.info('Authentication (on SMB2) failed. Account has been locked due to too many invalid logon attempts.')
                elif message.status == 0xc0000072: # STATUS_ACCOUNT_DISABLED
                    self.log.info('Authentication (on SMB2) failed. Account has been disabled.')
                elif (message.status == 0xc000006f    # STATUS_INVALID_LOGON_HOURS
                    or message.status == 0xc000015b   # STATUS_LOGON_TYPE_NOT_GRANTED
                    or message.status == 0xc0000070): # STATUS_INVALID_WORKSTATION
                    self.log.info('Authentication (on SMB2) failed. Not allowed.')
                elif message.status == 0xc000018c:  # STATUS_TRUSTED_DOMAIN_FAILURE
                    self.log.info('Authentication (on SMB2) failed. Domain not trusted.')
                elif message.status == 0xc000018d:  # STATUS_TRUSTED_RELATIONSHIP_FAILURE
                    self.log.info('Authentication (on SMB2) failed. Workstation not trusted.')
                else:
                    raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status,

            if message.isAsync:
                if message.status == 0x00000103:  # STATUS_PENDING
                    req = self.pending_requests.pop(message.mid, None)
                    if req:
                        self.async_requests[message.async_id] = req
                else: # All other status including SUCCESS
                    req = self.async_requests.pop(message.async_id, None)
                    if req:
                        req.callback(message, **req.kwargs)
                        return True
            else:
                req = self.pending_requests.pop(message.mid, None)
                if req:
                    req.callback(message, **req.kwargs)
                    return True

    def _updateServerInfo_SMB2(self, payload):
        self.capabilities = payload.capabilities
        self.security_mode = payload.security_mode
        self.max_transact_size = payload.max_transact_size
        self.max_read_size = payload.max_read_size
        self.max_write_size = payload.max_write_size
        self.use_plaintext_authentication = False   # SMB2 never allows plaintext authentication

    def _handleNegotiateResponse_SMB2(self, message):
        ntlm_data = ntlm.generateNegotiateMessage()
        blob = securityblob.generateNegotiateSecurityBlob(ntlm_data)
        self._sendSMBMessage(SMB2Message(SMB2SessionSetupRequest(blob)))

    def _handleSessionChallenge_SMB2(self, message, ntlm_token):
        server_challenge, server_flags, server_info = ntlm.decodeChallengeMessage(ntlm_token)

        self.log.info('Performing NTLMv2 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))

        if self.use_ntlm_v2:
            self.log.info('Performing NTLMv2 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))
            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV2(self.password,
                                                                                                         self.username,
                                                                                                         server_challenge,
                                                                                                         server_info,
                                                                                                         self.domain)

        else:
            self.log.info('Performing NTLMv1 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))
            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV1(self.password, server_challenge, True)

        ntlm_data, session_signing_key = ntlm.generateAuthenticateMessage(server_flags,
                                                                          nt_challenge_response,
                                                                          lm_challenge_response,
                                                                          session_key,
                                                                          self.username,
                                                                          self.domain,
                                                                          self.my_name)

        if self.log.isEnabledFor(logging.DEBUG):
            self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response))
            self.log.debug('LM challenge response is "%s" (%d bytes)', binascii.hexlify(lm_challenge_response), len(lm_challenge_response))

        blob = securityblob.generateAuthSecurityBlob(ntlm_data)

        if self.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED:
            self.log.info('Server requires all SMB messages to be signed')
            self.is_signing_active = (self.sign_options != SMB.SIGN_NEVER)
        elif self.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED:
            self.log.info('Server supports SMB signing')
            self.is_signing_active = (self.sign_options == SMB.SIGN_WHEN_SUPPORTED)
        else:
            self.is_signing_active = False

            self.log.info("SMB signing activated. All SMB messages will be signed.")
            self.signing_session_key = session_signing_key
            if self.log.isEnabledFor(logging.DEBUG):
                self.log.info("SMB signing key is %s", binascii.hexlify(self.signing_session_key))

            if self.capabilities & CAP_EXTENDED_SECURITY:
                self.signing_challenge_response = None
            else:
                self.signing_challenge_response = blob
        else:
            self.log.info("SMB signing deactivated. SMB messages will NOT be signed.")

    def _listShares_SMB2(self, callback, errback, timeout = 30):
        if not self.has_authenticated:
            raise NotReadyError('SMB connection not authenticated')

        path = 'IPC$'
        messages_history = [ ]

        def connectSrvSvc(tid):
            m = SMB2Message(SMB2CreateRequest('srvsvc',
                                              file_attributes = 0,
                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
                                              impersonation = SEC_IMPERSONATE,
                                              create_options = FILE_NON_DIRECTORY_FILE | FILE_OPEN_NO_RECALL,
                                              create_disp = FILE_OPEN))

            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback, tid = tid)
            messages_history.append(m)

        def connectSrvSvcCB(create_message, **kwargs):
            messages_history.append(create_message)
            if create_message.status == 0:
                call_id = self._getNextRPCCallID()
                # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706]
                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
                data_bytes = \
                    binascii.unhexlify(b"""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(b' ', b'')) + \
                    struct.pack('<I', call_id) + \
                    binascii.unhexlify(b"""
b8 10 b8 10 00 00 00 00 02 00 00 00 00 00 01 00
c8 4f 32 4b 70 16 d3 01 12 78 5a 47 bf 6e e1 88
03 00 00 00 04 5d 88 8a eb 1c c9 11 9f e8 08 00
2b 10 48 60 02 00 00 00 01 00 01 00 c8 4f 32 4b
70 16 d3 01 12 78 5a 47 bf 6e e1 88 03 00 00 00
2c 1c b7 6c 12 98 40 45 03 00 00 00 00 00 00 00
01 00 00 00
""".replace(b' ', b'').replace(b'\n', b''))
                m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0))
                m.tid = kwargs['tid']
                self._sendSMBMessage(m)
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid)
                messages_history.append(m)
            else:
                errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history))

        def rpcBindCB(trans_message, **kwargs):
            messages_history.append(trans_message)
            if trans_message.status == 0:
                m = SMB2Message(SMB2ReadRequest(kwargs['fid'], read_len = 1024, read_offset = 0))
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, tid = kwargs['tid'], fid = kwargs['fid'])
            else:
                closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint')

        def rpcReadCB(read_message, **kwargs):
            messages_history.append(read_message)
            if read_message.status == 0:

                padding = b''
                remote_name = '\\\\' + self.remote_name
                server_len = len(remote_name) + 1
                server_bytes_len = server_len * 2
                if server_len % 2 != 0:
                    padding = b'\0\0'
                    server_bytes_len += 2

                # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
                    binascii.unhexlify(b"""05 00 00 03 10 00 00 00""".replace(b' ', b'')) + \
                    struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \
                    binascii.unhexlify(b"""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(b' ', b'')) + \
                    struct.pack('<III', server_len, 0, server_len) + \
                    (remote_name + '\0').encode('UTF-16LE') + padding + \
01 00 00 00 01 00 00 00 04 00 02 00 00 00 00 00
00 00 00 00 ff ff ff ff 08 00 02 00 00 00 00 00
                m = SMB2Message(SMB2IoctlRequest(kwargs['fid'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes))
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, tid = kwargs['tid'], fid = kwargs['fid'])
            else:
                closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint')

        def listShareResultsCB(result_message, **kwargs):
            messages_history.append(result_message)
            if result_message.status == 0:
                # The payload.data_bytes will contain the results of the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
                data_bytes = result_message.payload.out_data

                if data_bytes[3] & 0x02 == 0:
                    sendReadRequest(kwargs['tid'], kwargs['fid'], data_bytes)
                else:
                    decodeResults(kwargs['tid'], kwargs['fid'], data_bytes)
            else:
                closeFid(kwargs['tid'], kwargs['fid'])
                errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))

        def decodeResults(tid, fid, data_bytes):
            shares_count = struct.unpack('<I', data_bytes[36:40])[0]
            results = [ ]     # A list of SharedDevice instances
            offset = 36 + 12  # You need to study the byte stream to understand the meaning of these constants
            for i in range(0, shares_count):
                results.append(SharedDevice(struct.unpack('<I', data_bytes[offset+4:offset+8])[0], None, None))
                offset += 12

                max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
                results[i].name = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')

                if length % 2 != 0:
                    offset += (length * 2 + 2)
                else:
                    offset += (length * 2)

                results[i].comments = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')

                else:

            closeFid(tid, fid)
            callback(results)

        def sendReadRequest(tid, fid, data_bytes):
            read_count = min(4280, self.max_read_size)
            m = SMB2Message(SMB2ReadRequest(fid, 0, read_count))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback,
                                                           tid = tid, fid = fid, data_bytes = data_bytes)

        def readCB(read_message, **kwargs):
                data_bytes = read_message.payload.data

                    sendReadRequest(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
                else:
                    decodeResults(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
            else:

        def closeFid(tid, fid, results = None, error = None):
            m = SMB2Message(SMB2CloseRequest(fid))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error)

        def closeCB(close_message, **kwargs):
            if kwargs['results'] is not None:
                callback(kwargs['results'])
            elif kwargs['error'] is not None:
                errback(OperationFailure(kwargs['error'], messages_history))

        if path not in self.connected_trees:
            def connectCB(connect_message, **kwargs):
                messages_history.append(connect_message)
                if connect_message.status == 0:
                    self.connected_trees[path] = connect_message.tid
                    connectSrvSvc(connect_message.tid)
                else:
                    errback(OperationFailure('Failed to list shares: Unable to connect to IPC$', messages_history))

            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), path )))
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)
        else:
            connectSrvSvc(self.connected_trees[path])

    def _listPath_SMB2(self, service_name, path, callback, errback, search, pattern, timeout = 30):

        path = path.replace('/', '\\')
        if path.startswith('\\'):
            path = path[1:]
        if path.endswith('\\'):
            path = path[:-1]

        def sendCreate(tid):
            create_context_data = binascii.unhexlify(b"""
28 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
44 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
00 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
00 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
51 46 69 64 00 00 00 00
            m = SMB2Message(SMB2CreateRequest(path,
                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                                              create_options = FILE_DIRECTORY_FILE,
                                              create_disp = FILE_OPEN,
                                              create_context_data = create_context_data))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid)

        def createCB(create_message, **kwargs):
                sendQuery(kwargs['tid'], create_message.payload.fid, b'')
            elif create_message.status == 0xC0000034: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID
                errback(OperationFailure('Failed to list %s on %s: Path not found' % ( path, service_name ), messages_history))
            else:
                errback(OperationFailure('Failed to list %s on %s: Unable to open directory' % ( path, service_name ), messages_history))

        def sendQuery(tid, fid, data_buf):
            m = SMB2Message(SMB2QueryDirectoryRequest(fid, pattern,
                                                      info_class = 0x25,   # FileIdBothDirectoryInformation
                                                      flags = 0,
                                                      output_buf_len = self.max_transact_size))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = tid, fid = fid, data_buf = data_buf)

        def queryCB(query_message, **kwargs):
            messages_history.append(query_message)
            if query_message.status == 0:
                data_buf = decodeQueryStruct(kwargs['data_buf'] + query_message.payload.data)
                sendQuery(kwargs['tid'], kwargs['fid'], data_buf)
            elif query_message.status == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE
                # If there are no matching files, we just treat as success instead of failing
                closeFid(kwargs['tid'], kwargs['fid'], results = results)
            elif query_message.status == 0x80000006:  # STATUS_NO_MORE_FILES
            else:
                closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status)

        def decodeQueryStruct(data_bytes):
            # FileIdBothDirectoryInformation structure. See [MS-SMB]: 2.2.8.1.3 and [MS-FSCC]: 2.4.17
            info_format = '<IIQQQQQQIIIBB24sHQ'
            info_size = struct.calcsize(info_format)

            data_length = len(data_bytes)
            offset = 0
            while offset < data_length:
                if offset + info_size > data_length:
                    return data_bytes[offset:]

                next_offset, _, \
                create_time, last_access_time, last_write_time, last_attr_change_time, \
                file_size, alloc_size, file_attributes, filename_length, ea_size, \
                short_name_length, _, short_name, _, file_id = struct.unpack(info_format, data_bytes[offset:offset+info_size])

                offset2 = offset + info_size
                if offset2 + filename_length > data_length:

                filename = data_bytes[offset2:offset2+filename_length].decode('UTF-16LE')
                short_name = short_name[:short_name_length].decode('UTF-16LE')

                accept_result = False
                if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc
                    accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL)
                else:
                    accept_result = (file_attributes & search) > 0
                if accept_result:
                    results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time),
                                              convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time),
                                              file_size, alloc_size, file_attributes, short_name, filename, file_id))

                if next_offset:
                    offset += next_offset
                else:
                    break
            return b''

            m.tid = tid

                if kwargs['error'] == 0xC000000F:  # [MS-ERREF]: STATUS_NO_SUCH_FILE
                    # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files
                    callback([ ])
                else:
                    errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))

        if service_name not in self.connected_trees:
                    self.connected_trees[service_name] = connect_message.tid
                    sendCreate(connect_message.tid)
                else:
                    errback(OperationFailure('Failed to list %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
        else:
            sendCreate(self.connected_trees[service_name])

    def _getAttributes_SMB2(self, service_name, path, callback, errback, timeout = 30):

            create_context_data = binascii.unhexlify("""
""".replace(' ', '').replace('\n', ''))
                                              create_options = 0,
            m.tid = tid

                p = create_message.payload
                filename = self._extractLastPathComponent(path)
                info = SharedFile(p.create_time, p.lastaccess_time, p.lastwrite_time, p.change_time,
                                  p.file_size, p.allocation_size, p.file_attributes,
                                  filename, filename)
                closeFid(kwargs['tid'], p.fid, info = info)
            else:
                errback(OperationFailure('Failed to get attributes for %s on %s: Unable to open remote file object' % ( path, service_name ), messages_history))

        def closeFid(tid, fid, info = None, error = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, info = info, error = error)

            if kwargs['info'] is not None:
                callback(kwargs['info'])
                errback(OperationFailure('Failed to get attributes for %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))

                else:
                    errback(OperationFailure('Failed to get attributes for %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _getSecurity_SMB2(self, service_name, path, callback, errback, timeout = 30):

                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,
            m.tid = tid

                m = SMB2Message(SMB2QueryInfoRequest(create_message.payload.fid,
                                                     flags = 0,
                                                     additional_info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                                     info_type = SMB2_INFO_SECURITY,
                                                     file_info_class = 0, # [MS-SMB2] 2.2.37, 3.2.4.12
                                                     input_buf = '',
                                                     output_buf_len = self.max_transact_size))
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid)
            else:
                errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to open file or directory' % ( path, service_name ), messages_history))

                security = SecurityDescriptor.from_bytes(query_message.payload.data)
                closeFid(kwargs['tid'], kwargs['fid'], result = security)
            else:

        def closeFid(tid, fid, result = None, error = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, result = result, error = error)

            if kwargs['result'] is not None:
                callback(kwargs['result'])
                errback(OperationFailure('Failed to get the security descriptor of %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))

                else:
                    errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _retrieveFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30):
        return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0, -1, timeout)

    def _retrieveFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):

                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
                                              create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE,
            m.tid = tid

                                                     flags = 0,
                                                     additional_info = 0,
                                                     info_type = SMB2_INFO_FILE,
                                                     file_info_class = 0x05,  # FileStandardInformation [MS-FSCC] 2.4
                                                     input_buf = b'',
                                                     output_buf_len = 4096))
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, infoCB, errback,
                                                               tid = kwargs['tid'],
                                                               fid = create_message.payload.fid,
                                                               file_attributes = create_message.payload.file_attributes)
            else:
                errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history))

        def infoCB(info_message, **kwargs):
            messages_history.append(info_message)
            if info_message.status == 0:
                file_len = struct.unpack('<Q', info_message.payload.data[8:16])[0]
                if max_length == 0 or starting_offset > file_len:
                    closeFid(info_message.tid, kwargs['fid'])
                    callback(( file_obj, kwargs['file_attributes'], 0 ))  # Note that this is a tuple of 3-elements
                else:
                    remaining_len = max_length
                    if remaining_len < 0:
                        remaining_len = file_len
                    if starting_offset + remaining_len > file_len:
                        remaining_len = file_len - starting_offset
                    sendRead(kwargs['tid'], kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes'])
            else:
                errback(OperationFailure('Failed to retrieve %s on %s: Unable to retrieve information on file' % ( path, service_name ), messages_history))

        def sendRead(tid, fid, offset, remaining_len, read_len, file_attributes):
            read_count = min(self.max_read_size, remaining_len)
            m = SMB2Message(SMB2ReadRequest(fid, offset, read_count))
            m.tid = tid
                                                           tid = tid, fid = fid, offset = offset,
                                                           remaining_len = remaining_len,
                                                           read_len = read_len,
                                                           file_attributes = file_attributes)

            # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history.
                data_len = read_message.payload.data_length
                file_obj.write(read_message.payload.data)

                remaining_len = kwargs['remaining_len'] - data_len

                if remaining_len > 0:
                    sendRead(kwargs['tid'], kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes'])
                else:
                    closeFid(kwargs['tid'], kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len ))
            else:
                messages_history.append(read_message)
                closeFid(kwargs['tid'], kwargs['fid'], error = read_message.status)

        def closeFid(tid, fid, ret = None, error = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, ret = ret, error = error)

            if kwargs['ret'] is not None:
                callback(kwargs['ret'])
                errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history))

                else:
                    errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _storeFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30):
        self._storeFileFromOffset_SMB2(service_name, path, file_obj, callback, errback, 0, True, timeout)

    def _storeFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30):

00 00 00 00 00 00 00 00 20 00 00 00 10 00 04 00
00 00 18 00 08 00 00 00 41 6c 53 69 00 00 00 00
85 62 00 00 00 00 00 00 18 00 00 00 10 00 04 00
                                              file_attributes = ATTR_ARCHIVE,
                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | SYNCHRONIZE,
                                              share_access = 0,
                                              create_disp = FILE_OVERWRITE_IF if truncate else FILE_OPEN_IF,
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)

            create_message.tid = kwargs['tid']
                sendWrite(create_message.tid, create_message.payload.fid, starting_offset)
            else:
                errback(OperationFailure('Failed to store %s on %s: Unable to open file' % ( path, service_name ), messages_history))

        def sendWrite(tid, fid, offset):
            write_count = self.max_write_size
            data = file_obj.read(write_count)
            data_len = len(data)
            if data_len > 0:
                m = SMB2Message(SMB2WriteRequest(fid, data, offset))
                m.tid = tid
                self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, tid = tid, fid = fid, offset = offset+data_len)
            else:
                closeFid(tid, fid, offset = offset)

        def writeCB(write_message, **kwargs):
            # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history.
            if write_message.status == 0:
                sendWrite(kwargs['tid'], kwargs['fid'], kwargs['offset'])
            else:
                messages_history.append(write_message)
                errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history))

        def closeFid(tid, fid, error = None, offset = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, fid = fid, offset = offset, error = error)

            if kwargs['offset'] is not None:
                callback(( file_obj, kwargs['offset'] ))  # Note that this is a tuple of 2-elements

                else:
                    errback(OperationFailure('Failed to store %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
        else:

    def _deleteFiles_SMB2(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30):

        pattern = None
        path = path_file_pattern.replace('/', '\\')
        else:
            path_components = path.split('\\')
            if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1:
                path = '\\'.join(path_components[:-1])
                pattern = path_components[-1]
        messages_history, files_queue = [ ], [ ]

        if pattern is None:
            if len(path_components) > 1:
                files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] ))
            else:
                files_queue.append(( '', path ))

        def deleteCB(path):
            if files_queue:
                p, filename = files_queue.pop(0)
                if filename:
                    if p:
                        filename = p + '\\' + filename
                    self._deleteFiles_SMB2__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout)
                else:
                    self._deleteDirectory_SMB2(service_name, p, deleteCB, errback, timeout)
            else:
                callback(path_file_pattern)

        def listCB(files_list):
            files_queue.extend(files_list)
            deleteCB(None)

                    if files_queue:
                        deleteCB(None)
                    else:
                        self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout)
                else:
                    errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:
                deleteCB(None)
            else:
                self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout)

    def _deleteFiles_SMB2__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30):
        folder_queue = [ ]
        files_list = [ ]
        current_path = [ 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

        def listCB(results):
            files = [ ]
            for f in filter(lambda x: x.filename not in [ '.', '..' ], results):
                if f.isDirectory:
                    if delete_matching_folders:
                        folder_queue.append(current_path[0]+'\\'+f.filename)
                else:
                    files.append(( current_path[0], f.filename ))
            if current_path[0]!=path and delete_matching_folders:
                files.append(( current_path[0], None ))

            if files:
                files_list[0:0] = files

            if folder_queue:
                p = folder_queue.pop()
                current_path[0] = p
                self._listPath_SMB2(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30)
            else:
                callback(files_list)

        self._listPath_SMB2(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout)

    def _deleteFiles_SMB2__del(self, service_name, tid, path, callback, errback, timeout = 30):

    28 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
    44 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
    00 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
    00 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
    51 46 69 64 00 00 00 00
    """.replace(b' ', b'').replace(b'\n', b''))
                                              access_mask = DELETE | FILE_READ_ATTRIBUTES,
                                              create_options = FILE_NON_DIRECTORY_FILE,
            m.tid = tid

        def createCB(open_message, **kwargs):
            open_message.tid = kwargs['tid']
            messages_history.append(open_message)
            if open_message.status == 0:
                sendDelete(open_message.tid, open_message.payload.fid)
            elif open_message.status == 0xc0000034:  # STATUS_OBJECT_NAME_NOT_FOUND
                callback(path)
            elif open_message.status == 0xC00000BA:  # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY
                errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history))
            else:
                errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history))

        def sendDelete(tid, fid):
            m = SMB2Message(SMB2SetInfoRequest(fid,
                                               additional_info = 0,
                                               info_type = SMB2_INFO_FILE,
                                               file_info_class = 0x0d,  # SMB2_FILE_DISPOSITION_INFO
                                               data = b'\x01'))
            # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4.11
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, fid = fid)

        def deleteCB(delete_message, **kwargs):
            messages_history.append(delete_message)
            if delete_message.status == 0:
                closeFid(kwargs['tid'], kwargs['fid'], status = 0)
            else:
                closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status)

        def closeFid(tid, fid, status = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, status = status)

            if kwargs['status'] == 0:
            else:
                errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history))

        sendCreate(tid)

    def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, file_attributes = ATTR_NORMAL, timeout = 30):

                                              access_mask = FILE_WRITE_ATTRIBUTES,
            m.tid = tid

                sendReset(kwargs['tid'], open_message.payload.fid)
            else:
                errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to open file' % ( path, service_name ), messages_history))

        def sendReset(tid, fid):
                                               file_info_class = 4,  # FileBasicInformation
                                               data = struct.pack('qqqqii', 0, 0, 0, 0, file_attributes, 0)))
            # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4, [MS-FSCC]: 2.4.7, [MS-FSCC]: 2.6
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, tid = tid, fid = fid)

        def resetCB(reset_message, **kwargs):
            messages_history.append(reset_message)
            if reset_message.status == 0:
            else:
                closeFid(kwargs['tid'], kwargs['fid'], status = reset_message.status)

            m.tid = tid

            else:
                errback(OperationFailure('Failed to reset attributes of %s on %s: Reset failed' % ( path, service_name ), messages_history))

                else:
                    errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _createDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30):

                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | READ_CONTROL | DELETE | SYNCHRONIZE,
                                              create_options = FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                              create_disp = FILE_CREATE,
            m.tid = tid

                closeFid(kwargs['tid'], create_message.payload.fid)
            else:
                errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history))

        def closeFid(tid, fid):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback)

            callback(path)

                else:
                    errback(OperationFailure('Failed to create directory %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _deleteDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30):

            m.tid = tid

                sendDelete(kwargs['tid'], open_message.payload.fid)
            else:
                errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( path, service_name ), messages_history))

            m.tid = tid

            else:

            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status)

            else:

                else:

        else:

    def _rename_SMB2(self, service_name, old_path, new_path, callback, errback, timeout = 30):

        new_path = new_path.replace('/', '\\')
        if new_path.startswith('\\'):
            new_path = new_path[1:]
        if new_path.endswith('\\'):
            new_path = new_path[:-1]

        old_path = old_path.replace('/', '\\')
        if old_path.startswith('\\'):
            old_path = old_path[1:]
        if old_path.endswith('\\'):
            old_path = old_path[:-1]

            m = SMB2Message(SMB2CreateRequest(old_path,
                                              access_mask = DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                                              create_options = FILE_SYNCHRONOUS_IO_NONALERT,
            m.tid = tid

                sendRename(kwargs['tid'], create_message.payload.fid)
            else:
                errback(OperationFailure('Failed to rename %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history))

        def sendRename(tid, fid):
            data = b'\x00'*16 + struct.pack('<I', len(new_path)*2) + new_path.encode('UTF-16LE')
                                               file_info_class = 0x0a,  # SMB2_FILE_RENAME_INFO
                                               data = data))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, tid = tid, fid = fid)

        def renameCB(rename_message, **kwargs):
            messages_history.append(rename_message)
            if rename_message.status == 0:
            else:
                closeFid(kwargs['tid'], kwargs['fid'], status = rename_message.status)

            m.tid = tid

                callback(( old_path, new_path ))
            else:
                errback(OperationFailure('Failed to rename %s on %s: Rename failed' % ( old_path, service_name ), messages_history))

                else:
                    errback(OperationFailure('Failed to rename %s on %s: Unable to connect to shared device' % ( old_path, service_name ), messages_history))

        else:

    def _listSnapshots_SMB2(self, service_name, path, callback, errback, timeout = 30):

00 00 00 00 00 00 00 00 00 00 00 00 10 00 04 00
                                              access_mask = FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
            m.tid = tid

                sendEnumSnapshots(kwargs['tid'], create_message.payload.fid)
            else:
                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history))

        def sendEnumSnapshots(tid, fid):
            m = SMB2Message(SMB2IoctlRequest(fid,
                                             ctlcode = 0x00144064,  # FSCTL_SRV_ENUMERATE_SNAPSHOTS
                                             flags = 0x0001,
                                             in_data = b''))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, enumSnapshotsCB, errback, tid = tid, fid = fid)

        def enumSnapshotsCB(enum_message, **kwargs):
            messages_history.append(enum_message)
            if enum_message.status == 0:
                results = [ ]
                snapshots_count = struct.unpack('<I', enum_message.payload.out_data[4:8])[0]
                for i in range(0, snapshots_count):
                    s = enum_message.payload.out_data[12+i*50:12+48+i*50].decode('UTF-16LE')
                    results.append(datetime(*list(map(int, ( s[5:9], s[10:12], s[13:15], s[16:18], s[19:21], s[22:24] )))))
            else:
                closeFid(kwargs['tid'], kwargs['fid'], status = enum_message.status)

        def closeFid(tid, fid, status = None, results = None):
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status, results = results)

            else:
                errback(OperationFailure('Failed to list snapshots %s on %s: List failed' % ( path, service_name ), messages_history))

                else:
                    errback(OperationFailure('Failed to list snapshots %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:

    def _echo_SMB2(self, data, callback, errback, timeout = 30):

        def echoCB(echo_message, **kwargs):
            messages_history.append(echo_message)
            if echo_message.status == 0:
                callback(data)
            else:
                errback(OperationFailure('Echo failed', messages_history))

        m = SMB2Message(SMB2EchoRequest())
        self._sendSMBMessage(m)
        self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback)
        messages_history.append(m)

    #
    # SMB1 Methods Family
    #

    def _sendSMBMessage_SMB1(self, smb_message):
            smb_message.mid = self._getNextMID_SMB1()
        if not smb_message.uid:
            smb_message.uid = self.uid
            smb_message.flags2 |= SMB_FLAGS2_SMB_SECURITY_SIGNATURE

            # Increment the next_signing_id as described in [MS-CIFS] 3.2.4.1.3
            smb_message.security = self.next_signing_id
            self.next_signing_id += 2  # All our defined messages currently have responses, so always increment by 2

            md = ntlm.MD5(self.signing_session_key)
            if self.signing_challenge_response:
                md.update(self.signing_challenge_response)
            md.update(raw_data)
            signature = md.digest()[:8]

            self.log.debug('MID is %d. Signing ID is %d. Signature is %s. Total raw message is %d bytes', smb_message.mid, smb_message.security, binascii.hexlify(signature), len(raw_data))
            smb_message.raw_data = raw_data[:14] + signature + raw_data[22:]
        else:

    def _getNextMID_SMB1(self):
        if self.mid >= 0xFFFF: # MID cannot be 0xFFFF. [MS-CIFS]: 2.2.1.6.2
            # We don't use MID of 0 as MID can be reused for SMB_COM_TRANSACTION2_SECONDARY messages
            # where if mid=0, _sendSMBMessage will re-assign new MID values again
            self.mid = 1

    def _updateState_SMB1(self, message):
            if message.command == SMB_COM_NEGOTIATE:
                if not message.status.hasError:
                    self.log.info('SMB dialect negotiation successful (ExtendedSecurity:%s)', message.hasExtendedSecurity)
                else:
                    raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_NEGOTIATE' % message.status.internal_value,
            elif message.command == SMB_COM_SESSION_SETUP_ANDX:
                if message.hasExtendedSecurity:
                    if not message.status.hasError:
                        try:
                            result = securityblob.decodeAuthResponseSecurityBlob(message.payload.security_blob)
                            if result == securityblob.RESULT_ACCEPT_COMPLETED:
                                self.log.debug('SMB uid is now %d', message.uid)
                                self.uid = message.uid
                                self.has_authenticated = True
                                self.log.info('Authentication (with extended security) successful!')
                                self.onAuthOK()
                            else:
                                raise ProtocolError('SMB_COM_SESSION_SETUP_ANDX status is 0 but security blob negResult value is %d' % result, message.raw_data, message)
                        except securityblob.BadSecurityBlobError as ex:
                            raise ProtocolError(str(ex), message.raw_data, message)
                    elif message.status.internal_value == 0xc0000016:  # STATUS_MORE_PROCESSING_REQUIRED
                        try:
                            result, ntlm_token = securityblob.decodeChallengeSecurityBlob(message.payload.security_blob)
                            if result == securityblob.RESULT_ACCEPT_INCOMPLETE:
                                self._handleSessionChallenge(message, ntlm_token)
                        except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ) as ex:
                    elif (message.status.internal_value == 0xc000006d    # STATUS_LOGON_FAILURE
                        or message.status.internal_value == 0xc0000064   # STATUS_NO_SUCH_USER
                        or message.status.internal_value == 0xc000006a): # STATUS_WRONG_PASSWORD
                        self.has_authenticated = False
                        self.log.info('Authentication (with extended security) failed. Please check username and password.')
                        self.onAuthFailed()
                    elif (message.status.internal_value == 0xc0000193    # STATUS_ACCOUNT_EXPIRED
                        or message.status.internal_value == 0xC0000071): # STATUS_PASSWORD_EXPIRED
                        self.log.info('Authentication (with extended security) failed. Account or password has expired.')
                    elif message.status.internal_value == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT
                        self.log.info('Authentication (with extended security) failed. Account has been locked due to too many invalid logon attempts.')
                    elif message.status.internal_value == 0xc0000072: # STATUS_ACCOUNT_DISABLED
                        self.log.info('Authentication (with extended security) failed. Account has been disabled.')
                    elif (message.status.internal_value == 0xc000006f    # STATUS_INVALID_LOGON_HOURS
                        or message.status.internal_value == 0xc000015b   # STATUS_LOGON_TYPE_NOT_GRANTED
                        or message.status.internal_value == 0xc0000070): # STATUS_INVALID_WORKSTATION
                        self.log.info('Authentication (with extended security) failed. Not allowed.')
                    elif message.status.internal_value == 0xc000018c:  # STATUS_TRUSTED_DOMAIN_FAILURE
                        self.log.info('Authentication (with extended security) failed. Domain not trusted.')
                    elif message.status.internal_value == 0xc000018d:  # STATUS_TRUSTED_RELATIONSHIP_FAILURE
                        self.log.info('Authentication (with extended security) failed. Workstation not trusted.')
                    else:
                        raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status.internal_value,
                                            message.raw_data, message)
                else:
                    if message.status.internal_value == 0:
                        self.log.debug('SMB uid is now %d', message.uid)
                        self.uid = message.uid
                        self.has_authenticated = True
                        self.log.info('Authentication (without extended security) successful!')
                        self.onAuthOK()
                    else:
                        self.log.info('Authentication (without extended security) failed. Please check username and password')
            elif message.command == SMB_COM_TREE_CONNECT_ANDX:
                try:
                    req = self.pending_requests[message.mid]
                except KeyError:
                    pass
                else:
                        self.connected_trees[req.kwargs['path']] = message.tid

            req = self.pending_requests.pop(message.mid, None)
            if req:
                req.callback(message, **req.kwargs)
                return True

    def _updateServerInfo_SMB1(self, payload):
        self.max_raw_size = payload.max_raw_size
        self.max_buffer_size = payload.max_buffer_size
        self.max_mpx_count = payload.max_mpx_count
        self.use_plaintext_authentication = not bool(payload.security_mode & NEGOTIATE_ENCRYPT_PASSWORDS)

        if self.use_plaintext_authentication:
            self.log.warning('Remote server only supports plaintext authentication. Your password can be stolen easily over the network.')

    def _handleSessionChallenge_SMB1(self, message, ntlm_token):
        assert message.hasExtendedSecurity

        if message.uid and not self.uid:
            self.uid = message.uid

            self.log.info('Performing NTLMv2 authentication (with extended security) with server challenge "%s"', binascii.hexlify(server_challenge))

        else:
            self.log.info('Performing NTLMv1 authentication (with extended security) with server challenge "%s"', binascii.hexlify(server_challenge))

        ntlm_data, signing_session_key = ntlm.generateAuthenticateMessage(server_flags,

        self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__WithSecurityExtension(0, blob)))

        if self.security_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRE:
        elif self.security_mode & NEGOTIATE_SECURITY_SIGNATURES_ENABLE:
        else:

            self.signing_session_key = signing_session_key
            else:
        else:

    def _handleNegotiateResponse_SMB1(self, message):

        if message.hasExtendedSecurity or message.payload.supportsExtendedSecurity:
            ntlm_data = ntlm.generateNegotiateMessage()
            blob = securityblob.generateNegotiateSecurityBlob(ntlm_data)
            self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__WithSecurityExtension(message.payload.session_key, blob)))
        else:
            nt_password, _, _ = ntlm.generateChallengeResponseV1(self.password, message.payload.challenge, False)
            self.log.info('Performing NTLMv1 authentication (without extended security) with challenge "%s" and hashed password of "%s"',
                          binascii.hexlify(message.payload.challenge),
                          binascii.hexlify(nt_password))
            self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__NoSecurityExtension(message.payload.session_key,
                                                                                           self.username,
                                                                                           nt_password,
                                                                                           True,
                                                                                           self.domain)))

    def _listShares_SMB1(self, callback, errback, timeout = 30):

            m = SMBMessage(ComNTCreateAndxRequest('\\srvsvc',
                                                  flags = NT_CREATE_REQUEST_EXTENDED_RESPONSE,
                                                  access_mask = READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA,
                                                  share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
                                                  create_disp = FILE_OPEN,
                                                  create_options = FILE_OPEN_NO_RECALL | FILE_NON_DIRECTORY_FILE,
                                                  impersonation = SEC_IMPERSONATE,
                                                  security_flags = 0))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback)

            if not create_message.status.hasError:
                # See [MS-CIFS]: 2.2.5.6.1 for more information on TRANS_TRANSACT_NMPIPE (0x0026) parameters
                setup_bytes = struct.pack('<HH', 0x0026, create_message.payload.fid)
                    binascii.unhexlify(b"""05 00 0b 03 10 00 00 00 48 00 00 00""".replace(b' ', b'')) + \
b8 10 b8 10 00 00 00 00 01 00 00 00 00 00 01 00
2b 10 48 60 02 00 00 00""".replace(b' ', b'').replace(b'\n', b''))
                m = SMBMessage(ComTransactionRequest(max_params_count = 0,
                                                     max_data_count = 4280,
                                                     max_setup_count = 0,
                                                     data_bytes = data_bytes,
                                                     setup_bytes = setup_bytes))
                m.tid = create_message.tid
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid)
            else:

            if not trans_message.status.hasError:

                server_len = len(self.remote_name) + 1

                setup_bytes = struct.pack('<HH', 0x0026, kwargs['fid'])
                    (self.remote_name + '\0').encode('UTF-16LE') + padding + \
                m.tid = trans_message.tid
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid'])
            else:
                closeFid(trans_message.tid, kwargs['fid'])
                errback(OperationFailure('Failed to list shares: Unable to bind to Server Service RPC endpoint', messages_history))

            if not result_message.status.hasError:
                data_bytes = result_message.payload.data_bytes

                    sendReadRequest(result_message.tid, kwargs['fid'], data_bytes)
                else:
                    decodeResults(result_message.tid, kwargs['fid'], data_bytes)
            else:
                closeFid(result_message.tid, kwargs['fid'])

                else:

                else:

            read_count = min(4280, self.max_raw_size - 2)
            m = SMBMessage(ComReadAndxRequest(fid = fid,
                                              offset = 0,
                                              max_return_bytes_count = read_count,
                                              min_return_bytes_count = read_count))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, data_bytes = data_bytes)

            if not read_message.status.hasError:

                    sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
                else:
                    decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
            else:
                closeFid(read_message.tid, kwargs['fid'])

            m = SMBMessage(ComCloseRequest(fid))
            m.tid = tid

        def connectCB(connect_message, **kwargs):
            messages_history.append(connect_message)
            if not connect_message.status.hasError:
                self.connected_trees[path] = connect_message.tid
                connectSrvSvc(connect_message.tid)
            else:
                errback(OperationFailure('Failed to list shares: Unable to connect to IPC$', messages_history))

        m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), path ), SERVICE_ANY, ''))
        self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)

    def _listPath_SMB1(self, service_name, path, callback, errback, search, pattern, timeout = 30):

        if not path.endswith('\\'):
            path += '\\'

        def sendFindFirst(tid, support_dfs=False):
            setup_bytes = struct.pack('<H', 0x0001)  # TRANS2_FIND_FIRST2 sub-command. See [MS-CIFS]: 2.2.6.2.1
            params_bytes = \
                struct.pack('<HHHHI',
                            search & 0xFFFF, # SearchAttributes (need to restrict the values due to introduction of SMB_FILE_ATTRIBUTE_INCL_NORMAL)
                            100,    # SearchCount
                            0x0006, # Flags: SMB_FIND_CLOSE_AT_EOS | SMB_FIND_RETURN_RESUME_KEYS
                            0x0104, # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO
                            0x0000) # SearchStorageType (seems to be ignored by Windows)
            if support_dfs:
                params_bytes += ("\\" + self.remote_name + "\\" + service_name + path + pattern + '\0').encode('UTF-16LE')
            else:
                params_bytes += (path + pattern + '\0').encode('UTF-16LE')

            m = SMBMessage(ComTransaction2Request(max_params_count = 10,
                                                  max_data_count = 16644,
                                                  max_setup_count = 0,
                                                  params_bytes = params_bytes,
                                                  setup_bytes = setup_bytes))
            m.tid = tid
                m.flags2 |= SMB_FLAGS2_DFS
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findFirstCB, errback)

        def decodeFindStruct(data_bytes):
            # SMB_FIND_FILE_BOTH_DIRECTORY_INFO structure. See [MS-CIFS]: 2.2.8.1.7 and [MS-SMB]: 2.2.8.1.1
            info_format = '<IIQQQQQQIIIBB24s'

            offset = 0

                short_name_length, _, short_name = struct.unpack(info_format, data_bytes[offset:offset+info_size])

                short_name = short_name.decode('UTF-16LE')

                else:
                                              file_size, alloc_size, file_attributes, short_name, filename))

                else:
                    break
            return b''

        def findFirstCB(find_message, **kwargs):
            messages_history.append(find_message)
            if not find_message.status.hasError:
                if 'total_count' not in kwargs:
                    # TRANS2_FIND_FIRST2 response. [MS-CIFS]: 2.2.6.2.2
                    sid, search_count, end_of_search, _, last_name_offset = struct.unpack('<HHHHH', find_message.payload.params_bytes[:10])
                    kwargs.update({ 'sid': sid, 'end_of_search': end_of_search, 'last_name_offset': last_name_offset, 'data_buf': b'' })
                else:
                    sid, end_of_search, last_name_offset = kwargs['sid'], kwargs['end_of_search'], kwargs['last_name_offset']

                send_next = True
                if find_message.payload.data_bytes:
                    d = decodeFindStruct(kwargs['data_buf'] + find_message.payload.data_bytes)
                    if 'data_count' not in kwargs:
                        if len(find_message.payload.data_bytes) != find_message.payload.total_data_count:
                            kwargs.update({ 'data_count': len(find_message.payload.data_bytes),
                                            'total_count': find_message.payload.total_data_count,
                                            'data_buf': d,
                                            })
                            send_next = False
                    else:
                        kwargs['data_count'] += len(find_message.payload.data_bytes)
                        kwargs['total_count'] = min(find_message.payload.total_data_count, kwargs['total_count'])
                        kwargs['data_buf'] = d
                        if kwargs['data_count'] != kwargs['total_count']:

                if not send_next:
                    self.pending_requests[find_message.mid] = _PendingRequest(find_message.mid, expiry_time, findFirstCB, errback, **kwargs)
                elif end_of_search:
                    callback(results)
                else:
                    sendFindNext(find_message.tid, sid, 0, results[-1].filename, kwargs.get('support_dfs', False))
            else:
                if find_message.status.internal_value == 0xC000000F:  # [MS-ERREF]: STATUS_NO_SUCH_FILE
                else:
                    errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history))

        def sendFindNext(tid, sid, resume_key, resume_file, support_dfs):
            setup_bytes = struct.pack('<H', 0x0002)  # TRANS2_FIND_NEXT2 sub-command. See [MS-CIFS]: 2.2.6.3.1
                struct.pack('<HHHIH',
                            sid,        # SID
                            100,        # SearchCount
                            0x0104,     # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO
                            resume_key, # ResumeKey
                            0x0006)     # Flags: SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS
            params_bytes += (resume_file+'\0').encode('UTF-16LE')

            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findNextCB, errback, sid = sid, support_dfs = support_dfs)

        def findNextCB(find_message, **kwargs):
                    # TRANS2_FIND_NEXT2 response. [MS-CIFS]: 2.2.6.3.2
                    search_count, end_of_search, _, last_name_offset = struct.unpack('<HHHH', find_message.payload.params_bytes[:8])
                    kwargs.update({ 'end_of_search': end_of_search, 'last_name_offset': last_name_offset, 'data_buf': b'' })
                else:
                    end_of_search, last_name_offset = kwargs['end_of_search'], kwargs['last_name_offset']

                                            })
                    else:

                    self.pending_requests[find_message.mid] = _PendingRequest(find_message.mid, expiry_time, findNextCB, errback, **kwargs)
                else:
                    sendFindNext(find_message.tid, kwargs['sid'], 0, results[-1].filename, kwargs.get('support_dfs', False))
            else:
                errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history))

        def sendDFSReferral(tid):
            setup_bytes = struct.pack('<H', 0x0010)  # TRANS2_GET_DFS_REFERRAL sub-command. See [MS-CIFS]: 2.2.6.16.1
            params_bytes = struct.pack('<H', 3)      # Max referral level 3
            params_bytes += ("\\" + self.remote_name + "\\" + service_name).encode('UTF-16LE')

            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, dfsReferralCB, errback)

        def dfsReferralCB(dfs_message, **kwargs):
            sendFindFirst(dfs_message.tid, True)

                if not connect_message.status.hasError:
                    if connect_message.payload.optional_support & SMB_TREE_CONNECTX_SUPPORT_DFS:
                        sendDFSReferral(connect_message.tid)
                    else:
                        sendFindFirst(connect_message.tid, False)
                else:

            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
        else:
            sendFindFirst(self.connected_trees[service_name])

    def _getAttributes_SMB1(self, service_name, path, callback, errback, timeout = 30):

        def sendQuery(tid):
            setup_bytes = struct.pack('<H', 0x0005)  # TRANS2_QUERY_PATH_INFORMATION sub-command. See [MS-CIFS]: 2.2.6.6.1
                struct.pack('<HI',
                            0x0107, # SMB_QUERY_FILE_ALL_INFO ([MS-CIFS] 2.2.2.3.3)
                            0x0000) # Reserved
            params_bytes += (path + '\0').encode('UTF-16LE')

            m = SMBMessage(ComTransaction2Request(max_params_count = 2,
                                                  max_data_count = 65535,
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback)

            if not query_message.status.hasError:
                info_format = '<QQQQIIQQ'
                info_size = struct.calcsize(info_format)
                file_attributes, _, alloc_size, file_size = struct.unpack(info_format, query_message.payload.data_bytes[:info_size])

                info = SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time),
                                  file_size, alloc_size, file_attributes, filename, filename)
                callback(info)
            else:
                errback(OperationFailure('Failed to get attributes for %s on %s: Read failed' % ( path, service_name ), messages_history))

                    sendQuery(connect_message.tid)
                else:

        else:
            sendQuery(self.connected_trees[service_name])

    def _getSecurity_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30):
        raise NotReadyError('getSecurity is not yet implemented for SMB1')

    def _retrieveFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30):

    def _retrieveFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):

        def sendOpen(tid):
            m = SMBMessage(ComOpenAndxRequest(filename = path,
                                              access_mode = 0x0040,  # Sharing mode: Deny nothing to others
                                              open_mode = 0x0001,    # Failed if file does not exist
                                              search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM,
                                              timeout = timeout * 1000))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)

        def openCB(open_message, **kwargs):
            if not open_message.status.hasError:
                if max_length == 0:
                    closeFid(open_message.tid, open_message.payload.fid)
                    callback(( file_obj, open_message.payload.file_attributes, 0 ))
                else:
                    sendRead(open_message.tid, open_message.payload.fid, starting_offset, open_message.payload.file_attributes, 0, max_length)
            else:

        def sendRead(tid, fid, offset, file_attributes, read_len, remaining_len):
            read_count = self.max_raw_size - 2
                                              offset = offset,
                                              min_return_bytes_count = min(0xFFFF, read_count)))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, offset = offset, file_attributes = file_attributes,
                                                           read_len = read_len, remaining_len = remaining_len)

                read_len = kwargs['read_len']
                remaining_len = kwargs['remaining_len']
                if max_length > 0:
                    if data_len > remaining_len:
                        file_obj.write(read_message.payload.data[:remaining_len])
                        read_len += remaining_len
                        remaining_len = 0
                    else:
                        file_obj.write(read_message.payload.data)
                        remaining_len -= data_len
                        read_len += data_len
                else:
                    file_obj.write(read_message.payload.data)
                    read_len += data_len

                if (max_length > 0 and remaining_len <= 0) or data_len < (self.max_raw_size - 2):
                    closeFid(read_message.tid, kwargs['fid'])
                    callback(( file_obj, kwargs['file_attributes'], read_len ))  # Note that this is a tuple of 3-elements
                else:
                    sendRead(read_message.tid, kwargs['fid'], kwargs['offset']+data_len, kwargs['file_attributes'], read_len, remaining_len)
            else:

            m.tid = tid

                    sendOpen(connect_message.tid)
                else:

        else:
            sendOpen(self.connected_trees[service_name])

    def _storeFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30):
        self._storeFileFromOffset_SMB1(service_name, path, file_obj, callback, errback, 0, True, timeout)

    def _storeFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30):

                                              access_mode = 0x0041,  # Sharing mode: Deny nothing to others + Open for writing
                                              open_mode = 0x0012 if truncate else 0x0011,    # Create file if file does not exist. Overwrite or append depending on truncate parameter.
            m.tid = tid

                sendWrite(open_message.tid, open_message.payload.fid, starting_offset)
            else:

            # [MS-SMB] 2.2.4.5.2.2: The total SMB message size (inclusive of SMB header) must be not exceed the max_buffer_size.
            write_count = min(self.max_buffer_size-64, 0xFFFF-64) # SMB header is 32-bytes. We factor in another 32-bytes for the message parameter block
            data_bytes = file_obj.read(write_count)
            data_len = len(data_bytes)
                m = SMBMessage(ComWriteAndxRequest(fid = fid, offset = offset, data_bytes = data_bytes))
                m.tid = tid
                self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, fid = fid, offset = offset+data_len)
            else:
                closeFid(tid, fid)
                callback(( file_obj, offset ))  # Note that this is a tuple of 2-elements

            if not write_message.status.hasError:
                sendWrite(write_message.tid, kwargs['fid'], kwargs['offset'])
            else:
                closeFid(write_message.tid, kwargs['fid'])

            m.tid = tid

                else:

        else:

    def _deleteFiles_SMB1(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30):

        else:

            else:

                    if p:
                    self._deleteFiles_SMB1__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout)
                else:
                    self._deleteDirectory_SMB1(service_name, p, deleteCB, errback, timeout = 30)
            else:

                    else:
                        self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout)
                else:

        else:
            else:
                self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout)

    def _deleteFiles_SMB1__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30):

            files = [ ]
                else:

            if files:

                self._listPath_SMB1(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30)
            else:

        self._listPath_SMB1(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout)

    def _deleteFiles_SMB1__del(self, service_name, tid, path, callback, errback, timeout = 30):

        def sendDelete(tid):
            m = SMBMessage(ComDeleteRequest(filename_pattern = path,
                                            search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback)

            if not delete_message.status.hasError:
            elif delete_message.status.internal_value == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE
            elif delete_message.status.internal_value == 0xC00000BA: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY
            elif delete_message.status.internal_value == 0xC0000034: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID
                errback(OperationFailure('Failed to delete %s on %s: Path not found' % ( path, service_name ), messages_history))
            else:

        sendDelete(tid)

    def _resetFileAttributes_SMB1(self, service_name, path_file_pattern, callback, errback, file_attributes=ATTR_NORMAL, timeout = 30):
        raise NotReadyError('resetFileAttributes is not yet implemented for SMB1')

    def _createDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30):

            m = SMBMessage(ComCreateDirectoryRequest(path))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback)

            else:

                else:

        else:

    def _deleteDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30):

            m = SMBMessage(ComDeleteDirectoryRequest(path))
            m.tid = tid

            else:
                errback(OperationFailure('Failed to delete directory %s on %s: Delete failed' % ( path, service_name ), messages_history))

                    sendDelete(connect_message.tid)
                else:
                    errback(OperationFailure('Failed to delete directory %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))

        else:
            sendDelete(self.connected_trees[service_name])

    def _rename_SMB1(self, service_name, old_path, new_path, callback, errback, timeout = 30):

        def sendRename(tid):
            m = SMBMessage(ComRenameRequest(old_path = old_path,
                                            new_path = new_path,
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback)

            if not rename_message.status.hasError:
                callback(( old_path, new_path ))  # Note that this is a tuple of 2-elements
            else:

                    sendRename(connect_message.tid)
                else:

        else:
            sendRename(self.connected_trees[service_name])

    def _listSnapshots_SMB1(self, service_name, path, callback, errback, timeout = 30):

                                              search_attributes = 0,
            m.tid = tid

                sendEnumSnapshots(open_message.tid, open_message.payload.fid)
            else:
                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open path' % ( path, service_name ), messages_history))

            # [MS-CIFS]: 2.2.7.2
            # [MS-SMB]: 2.2.7.2.1
            setup_bytes = struct.pack('<IHBB',
                                      0x00144064,  # [MS-SMB]: 2.2.7.2.1
                                      fid,         # FID
                                      0x01,        # IsFctl
                                      0)           # IsFlags
            m = SMBMessage(ComNTTransactRequest(function = 0x0002,  # NT_TRANSACT_IOCTL. [MS-CIFS]: 2.2.7.2.1
                                                max_params_count = 0,
                                                max_data_count = 0xFFFF,
                                                max_setup_count = 0,
                                                setup_bytes = setup_bytes))
            m.tid = tid
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, enumSnapshotsCB, errback, tid = tid, fid = fid)

            if not enum_message.status.hasError:
                snapshots_count = struct.unpack('<I', enum_message.payload.data_bytes[4:8])[0]
                    s = enum_message.payload.data_bytes[12+i*50:12+48+i*50].decode('UTF-16LE')
                callback(results)
            else:
                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to list snapshots on path' % ( path, service_name ), messages_history))

            m.tid = tid

                else:

        else:

    def _echo_SMB1(self, data, callback, errback, timeout = 30):

        if not isinstance(data, type(b'')):
            raise TypeError('Echo data must be %s not %s' % (type(b'').__name__, type(data).__name__))

            if not echo_message.status.hasError:
                callback(echo_message.payload.data)
            else:

        m = SMBMessage(ComEchoRequest(echo_data = data))

    def _extractLastPathComponent(self, path):
        return path.replace('\\', '/').split('/')[-1]

class SharedDevice:
    """
    Contains information about a single shared device on the remote server.

    The following attributes are available:

    * name : An unicode string containing the name of the shared device
    * comments : An unicode string containing the user description of the shared device
    """

    # The following constants are taken from [MS-SRVS]: 2.2.2.4
    # They are used to identify the type of shared resource from the results from the NetrShareEnum in Server Service RPC
    DISK_TREE   = 0x00
    PRINT_QUEUE = 0x01
    COMM_DEVICE = 0x02
    IPC         = 0x03

    def __init__(self, type, name, comments):
        self._type = type
        self.name = name         #: An unicode string containing the name of the shared device
        self.comments = comments #: An unicode string containing the user description of the shared device

    @property
    def type(self):
        """
        Returns one of the following integral constants.
         - SharedDevice.DISK_TREE
         - SharedDevice.PRINT_QUEUE
         - SharedDevice.COMM_DEVICE
         - SharedDevice.IPC
        """
        return self._type & 0xFFFF

    @property
    def isSpecial(self):
        """
        Returns True if this shared device is a special share reserved for interprocess communication (IPC$)
        or remote administration of the server (ADMIN$). Can also refer to administrative shares such as
        C$, D$, E$, and so forth
        """
        return bool(self._type & 0x80000000)

    @property
    def isTemporary(self):
        """
        Returns True if this is a temporary share that is not persisted for creation each time the file server initializes.
        """
        return bool(self._type & 0x40000000)

    def __unicode__(self):
        return 'Shared device: %s (type:0x%02x comments:%s)' % (self.name, self.type, self.comments )

class SharedFile:
    """
    Contain information about a file/folder entry that is shared on the shared device.

    As an application developer, you should not need to instantiate a *SharedFile* instance directly in your application.
    These *SharedFile* instances are usually returned via a call to *listPath* method in :doc:`smb.SMBProtocol.SMBProtocolFactory<smb_SMBProtocolFactory>`.

    If you encounter *SharedFile* instance where its short_name attribute is empty but the filename attribute contains a short name which does not correspond
    to any files/folders on your remote shared device, it could be that the original filename on the file/folder entry on the shared device contains
    one of these prohibited characters: "\\/[]:+|<>=;?,* (see [MS-CIFS]: 2.2.1.1.1 for more details).

    * create_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server
    * last_access_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server
    * last_write_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server
    * last_attr_change_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server
    * file_size : File size in number of bytes
    * alloc_size : Total number of bytes allocated to store this file
    * file_attributes : A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py.
    * short_name : Unicode string containing the short name of this file (usually in 8.3 notation)
    * filename : Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters.
    * file_id : Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17
    """

    def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename, file_id=None):
        self.create_time = create_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server
        self.last_access_time = last_access_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server
        self.last_write_time = last_write_time    #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server
        self.last_attr_change_time = last_attr_change_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server
        self.file_size = file_size   #: File size in number of bytes
        self.alloc_size = alloc_size #: Total number of bytes allocated to store this file
        self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py.
        self.short_name = short_name #: Unicode string containing the short name of this file (usually in 8.3 notation)
        self.filename = filename     #: Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters.
        self.file_id = file_id       #: Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17

    @property
    def isDirectory(self):
        """A convenience property to return True if this file resource is a directory on the remote server"""
        return bool(self.file_attributes & ATTR_DIRECTORY)

    @property
    def isReadOnly(self):
        """A convenient property to return True if this file resource is read-only on the remote server"""
        return bool(self.file_attributes & ATTR_READONLY)

    @property
    def isNormal(self):
        """
        A convenient property to return True if this is a normal file.

        Note that pysmb defines a normal file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
        """
        return (self.file_attributes==ATTR_NORMAL) or ((self.file_attributes & 0xff)==0)

        return 'Shared file: %s (FileSize:%d bytes, isDirectory:%s)' % ( self.filename, self.file_size, self.isDirectory )

class _PendingRequest:

    def __init__(self, mid, expiry_time, callback, errback, **kwargs):
        self.mid = mid
        self.expiry_time = expiry_time
        self.callback = callback
        self.errback = errback
        self.kwargs = kwargs

import os, sys, struct, types, logging, binascii, time
from io import StringIO

# Set to True if you want to enable support for extended security. Required for Windows Vista and later
SUPPORT_EXTENDED_SECURITY = True

# Set to True if you want to enable SMB2 protocol.
SUPPORT_SMB2 = True

# Supported dialects
DIALECTS = [ ]
for i, ( name, dialect ) in enumerate([ ( 'NT_LAN_MANAGER_DIALECT', b'NT LM 0.12' ), ]):
    DIALECTS.append(dialect)
    globals()[name] = i

DIALECTS2 = [ ]
for i, ( name, dialect ) in enumerate([ ( 'SMB2_DIALECT', b'SMB 2.002' ) ]):
    DIALECTS2.append(dialect)
    globals()[name] = i + len(DIALECTS)

class UnsupportedFeature(Exception):
    """
    Raised when an supported feature is present/required in the protocol but is not
    currently supported by pysmb
    """
    pass

class ProtocolError(Exception):

    def __init__(self, message, data_buf = None, smb_message = None):
        self.message = message
        self.data_buf = data_buf
        self.smb_message = smb_message

    def __str__(self):
        b = StringIO()
        b.write(self.message + os.linesep)
        if self.smb_message:
            b.write('=' * 20 + ' SMB Message ' + '=' * 20 + os.linesep)
            b.write(str(self.smb_message))

        if self.data_buf:
            b.write('=' * 20 + ' SMB Data Packet (hex) ' + '=' * 20 + os.linesep)
            b.write(str(binascii.hexlify(self.data_buf)))
            b.write(os.linesep)

        return b.getvalue()

class SMB2ProtocolHeaderError(ProtocolError):

    def __init__(self):
        ProtocolError.__init__(self, "Packet header belongs to SMB2")

class OperationFailure(Exception):

    def __init__(self, message, smb_messages):
        self.smb_messages = smb_messages

        for idx, m in enumerate(self.smb_messages):
            b.write('=' * 20 + ' SMB Message %d ' % idx + '=' * 20 + os.linesep)
            b.write('SMB Header:' + os.linesep)
            b.write('-----------' + os.linesep)
            b.write(str(m))
            b.write('SMB Data Packet (hex):' + os.linesep)
            b.write('----------------------' + os.linesep)
            b.write(str(binascii.hexlify(m.raw_data)))

class SMBError:

        self.reset()

    def reset(self):
        self.internal_value = 0
        self.is_ntstatus = True

        if self.is_ntstatus:
            return 'NTSTATUS=0x%08X' % self.internal_value
        else:
            return 'ErrorClass=0x%02X ErrorCode=0x%04X' % ( self.internal_value >> 24, self.internal_value & 0xFFFF )

    @property
    def hasError(self):
        return self.internal_value != 0

class SMBMessage:

    HEADER_STRUCT_FORMAT = "<4sBIBHHQxxHHHHB"
    HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)

    log = logging.getLogger('SMB.SMBMessage')
    protocol = 1

    def __init__(self, payload = None):
        if payload:
            self.payload = payload
            self.payload.initMessage(self)

        b.write('Command: 0x%02X (%s) %s' % ( self.command, SMB_COMMAND_NAMES.get(self.command, '<unknown>'), os.linesep ))
        b.write('Status: %s %s' % ( str(self.status), os.linesep ))
        b.write('Flags: 0x%02X %s' % ( self.flags, os.linesep ))
        b.write('Flags2: 0x%04X %s' % ( self.flags2, os.linesep ))
        b.write('PID: %d %s' % ( self.pid, os.linesep ))
        b.write('UID: %d %s' % ( self.uid, os.linesep ))
        b.write('MID: %d %s' % ( self.mid, os.linesep ))
        b.write('TID: %d %s' % ( self.tid, os.linesep ))
        b.write('Security: 0x%016X %s' % ( self.security, os.linesep ))
        b.write('Parameters: %d bytes %s%s %s' % ( len(self.parameters_data), os.linesep, str(binascii.hexlify(self.parameters_data)), os.linesep ))
        b.write('Data: %d bytes %s%s %s' % ( len(self.data), os.linesep, str(binascii.hexlify(self.data)), os.linesep ))

        self.raw_data = b''
        self.command = 0
        self.status = SMBError()
        self.flags = 0
        self.flags2 = 0
        self.pid = 0
        self.tid = 0
        self.security = 0
        self.parameters_data = b''
        self.data = b''
        self.payload = None

    @property
    def isAsync(self):
        return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND)

    @property
    def isReply(self):
        return bool(self.flags & SMB_FLAGS_REPLY)

    @property
    def hasExtendedSecurity(self):
        return bool(self.flags2 & SMB_FLAGS2_EXTENDED_SECURITY)

    def encode(self):
        """
        Encode this SMB 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 SMB message
        """
        assert self.payload

        self.pid = os.getpid()
        self.payload.prepare(self)

        parameters_len = len(self.parameters_data)
        assert parameters_len % 2 == 0

        headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
                                   b'\xFFSMB', self.command, self.status.internal_value, self.flags,
                                   self.flags2, (self.pid >> 16) & 0xFFFF, self.security, self.tid,
                                   self.pid & 0xFFFF, self.uid, self.mid, int(parameters_len / 2))
        return headers_data + self.parameters_data + struct.pack('<H', len(self.data)) + self.data

    def decode(self, buf):
        """
        Decodes the SMB message in buf.
        All fields of the SMBMessage 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.

        @param buf: data containing one complete SMB 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 < self.HEADER_STRUCT_SIZE:
            # We need at least 32 bytes (header) + 1 byte (parameter count)
            raise ProtocolError('Not enough data to decode SMB header', buf)

        protocol, self.command, status, self.flags, \
        self.flags2, pid_high, self.security, self.tid, \
        pid_low, self.uid, self.mid, params_count = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])

        if protocol == b'\xFESMB':
            raise SMB2ProtocolHeaderError()
        if protocol != b'\xFFSMB':
            raise ProtocolError('Invalid 4-byte protocol field', buf)

        self.pid = (pid_high << 16) | pid_low
        self.status.internal_value = status
        self.status.is_ntstatus = bool(self.flags2 & SMB_FLAGS2_NT_STATUS)

        offset = self.HEADER_STRUCT_SIZE
        if buf_len < params_count * 2 + 2:
            # Not enough data in buf to decode up to body length
            raise ProtocolError('Not enough data. Parameters list decoding failed', buf)

        datalen_offset = offset + params_count*2
        body_len = struct.unpack('<H', buf[datalen_offset:datalen_offset+2])[0]
        if body_len > 0 and buf_len < (datalen_offset + 2 + body_len):
            # Not enough data in buf to decode body
            raise ProtocolError('Not enough data. Body decoding failed', buf)

        self.parameters_data = buf[offset:datalen_offset]

        if body_len > 0:
            self.data = buf[datalen_offset+2:datalen_offset+2+body_len]

        self.raw_data = buf
        self._decodePayload()

        return self.HEADER_STRUCT_SIZE + params_count * 2 + 2 + body_len

    def _decodePayload(self):
        if self.command == SMB_COM_READ_ANDX:
            self.payload = ComReadAndxResponse()
        elif self.command == SMB_COM_WRITE_ANDX:
            self.payload = ComWriteAndxResponse()
        elif self.command == SMB_COM_TRANSACTION:
            self.payload = ComTransactionResponse()
        elif self.command == SMB_COM_TRANSACTION2:
            self.payload = ComTransaction2Response()
        elif self.command == SMB_COM_OPEN_ANDX:
            self.payload = ComOpenAndxResponse()
        elif self.command == SMB_COM_NT_CREATE_ANDX:
            self.payload = ComNTCreateAndxResponse()
        elif self.command == SMB_COM_TREE_CONNECT_ANDX:
            self.payload = ComTreeConnectAndxResponse()
        elif self.command == SMB_COM_ECHO:
            self.payload = ComEchoResponse()
        elif self.command == SMB_COM_SESSION_SETUP_ANDX:
            self.payload = ComSessionSetupAndxResponse()
        elif self.command == SMB_COM_NEGOTIATE:
            self.payload = ComNegotiateResponse()

        if self.payload:
            self.payload.decode(self)

class Payload:

    DEFAULT_ANDX_PARAM_HEADER = b'\xFF\x00\x00\x00'
    DEFAULT_ANDX_PARAM_SIZE = 4

    def initMessage(self, message):
        # SMB_FLAGS2_UNICODE must always be enabled. Without this, almost all the Payload subclasses will need to be
        # rewritten to check for OEM/Unicode strings which will be tedious. Fortunately, almost all tested CIFS services
        # support SMB_FLAGS2_UNICODE by default.
        assert message.payload == self
        message.flags =  SMB_FLAGS_CASE_INSENSITIVE | SMB_FLAGS_CANONICALIZED_PATHS
        message.flags2 = SMB_FLAGS2_UNICODE | SMB_FLAGS2_NT_STATUS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_LONG_NAMES

        if SUPPORT_EXTENDED_SECURITY:
            message.flags2 |= SMB_FLAGS2_EXTENDED_SECURITY

    def prepare(self, message):
        raise NotImplementedError

    def decode(self, message):

class ComNegotiateRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.52.1
    - [MS-SMB]: 2.2.4.5.1
    """

        Payload.initMessage(self, message)
        message.command = SMB_COM_NEGOTIATE

        message.parameters_data = b''
        if SUPPORT_SMB2:
            message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS + DIALECTS2])
        else:
            message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS])

class ComNegotiateResponse(Payload):
    """
    Contains information on the SMB_COM_NEGOTIATE response from server

    After calling the decode method, each instance will contain the following attributes,
    - security_mode (integer)
    - max_mpx_count (integer)
    - max_number_vcs (integer)
    - max_buffer_size (long)
    - max_raw_size (long)
    - session_key (long)
    - capabilities (long)
    - system_time (long)
    - server_time_zone (integer)
    - challenge_length (integer)

    If the underlying SMB message's flag2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
    then the instance will have the following additional attributes,
    - challenge (string)
    - domain (unicode)

    If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
    - server_guid (string)
    - security_blob (string)

    - [MS-SMB]: 2.2.4.5.2.1
    - [MS-CIFS]: 2.2.4.52.2
    """

    PAYLOAD_STRUCT_FORMAT = '<HBHHIIIIQHB'
    PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        assert message.command == SMB_COM_NEGOTIATE

        if not message.isReply:
            raise ProtocolError('Not a SMB_COM_NEGOTIATE reply', message.raw_data, message)

        self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
        self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
        self.challenge_length = ( 0, ) * 10

        data_len = len(message.parameters_data)
        if data_len < 2:
            raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE dialect_index field', message.raw_data, message)

        self.dialect_index = struct.unpack('<H', message.parameters_data[:2])[0]
        if self.dialect_index == NT_LAN_MANAGER_DIALECT:
            if data_len != (0x11 * 2):
                raise ProtocolError('NT LAN Manager dialect selected in SMB_COM_NEGOTIATE but parameters bytes count (%d) does not meet specs' % data_len,
                                    message.raw_data, message)
            else:
                _, self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
                self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
                self.challenge_length = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
        elif self.dialect_index == 0xFFFF:
            raise ProtocolError('Server does not support any of the pysmb dialects. Please email pysmb to add in support for your OS',
                                message.raw_data, message)
        else:
            raise ProtocolError('Unknown dialect index (0x%04X)' % self.dialect_index, message.raw_data, message)

        data_len = len(message.data)
        if not message.hasExtendedSecurity:
            self.challenge, self.domain = '', ''
            if self.challenge_length > 0:
                if data_len >= self.challenge_length:
                    self.challenge = message.data[:self.challenge_length]

                    s = b''
                    offset = self.challenge_length
                    while offset < data_len:
                        _s = message.data[offset:offset+2]
                        if _s == b'\0\0':
                            self.domain = s.decode('UTF-16LE')
                            break
                        else:
                            s += _s
                            offset += 2
                else:
                    raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (without security extensions) Challenge field', message.raw_data, message)
        else:
            if data_len < 16:
                raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (with security extensions) ServerGUID field', message.raw_data, message)

            self.server_guid = message.data[:16]
            self.security_blob = message.data[16:]

    @property
    def supportsExtendedSecurity(self):
        return bool(self.capabilities & CAP_EXTENDED_SECURITY)

class ComSessionSetupAndxRequest__WithSecurityExtension(Payload):
    """
    - [MS-SMB]: 2.2.4.6.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHIHII'

    def __init__(self, session_key, security_blob):
        self.session_key = session_key
        self.security_blob = security_blob

        message.command = SMB_COM_SESSION_SETUP_ANDX

        message.flags2 |= SMB_FLAGS2_UNICODE

        cap = CAP_UNICODE | CAP_STATUS32 | CAP_EXTENDED_SECURITY | CAP_NT_SMBS

        message.parameters_data = \
            self.DEFAULT_ANDX_PARAM_HEADER + \
            struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                        16644, 10, 1, self.session_key, len(self.security_blob), 0, cap)

        message.data = self.security_blob
        if (SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data) + len(message.data)) % 2 != 0:
            message.data = message.data + b'\0'
        message.data = message.data + b'\0' * 4

class ComSessionSetupAndxRequest__NoSecurityExtension(Payload):
    """
    - [MS-CIFS]: 2.2.4.53.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHIHHII'

    def __init__(self, session_key, username, password, is_unicode, domain):
        self.is_unicode = is_unicode

        if self.is_unicode:
            message.flags2 |= SMB_FLAGS2_UNICODE
        else:
            message.flags2 &= (~SMB_FLAGS2_UNICODE & 0xFFFF)

        password_len = len(self.password)
                        16644, 10, 0, self.session_key,
                        (not self.is_unicode and password_len) or 0,
                        (self.is_unicode and password_len) or 0,
                        0,
                        CAP_UNICODE | CAP_LARGE_FILES | CAP_STATUS32)

        est_offset = SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data)  # To check if data until SMB paramaters are aligned to a 16-bit boundary

        message.data = self.password
        if (est_offset + len(message.data)) % 2 != 0 and message.flags2 & SMB_FLAGS2_UNICODE:

        if message.flags2 & SMB_FLAGS2_UNICODE:
            message.data = message.data + self.username.encode('UTF-16LE') + b'\0'
        else:
            message.data = message.data + str(self.username).encode('UTF-8') + b'\0'

            message.data = message.data + self.domain.encode('UTF-16LE') + b'\0\0' + 'pysmb'.encode('UTF-16LE') + b'\0\0'
        else:
            message.data = message.data + self.domain.encode('UTF-8') + b'\0pysmb\0'

class ComSessionSetupAndxResponse(Payload):
    """
    Contains information on the SMB_COM_SESSION_SETUP_ANDX response from server

    If the underlying SMB message's flags2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
    then the instance will have the following attributes,
    - action

    If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled
    and the message status is STATUS_MORE_PROCESSING_REQUIRED or equals to 0x00 (no error),
    - action
    - securityblob

    If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled but
    the message status is not STATUS_MORE_PROCESSING_REQUIRED

    - [MS-SMB]: 2.2.4.6.2
    - [MS-CIFS]: 2.2.4.53.2
    """

    NOSECURE_PARAMETER_STRUCT_FORMAT = '<BBHH'
    NOSECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(NOSECURE_PARAMETER_STRUCT_FORMAT)

    SECURE_PARAMETER_STRUCT_FORMAT = '<BBHHH'
    SECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(SECURE_PARAMETER_STRUCT_FORMAT)

        assert message.command == SMB_COM_SESSION_SETUP_ANDX
            if not message.status.hasError:
                if len(message.parameters_data) < self.NOSECURE_PARAMETER_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (no security extensions) parameters', message.raw_data, message)

                _, _, _, self.action = struct.unpack(self.NOSECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.NOSECURE_PARAMETER_STRUCT_SIZE])
        else:
            if not message.status.hasError or message.status.internal_value == 0xc0000016:   # STATUS_MORE_PROCESSING_REQUIRED
                if len(message.parameters_data) < self.SECURE_PARAMETER_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) parameters', message.raw_data, message)

                _, _, _, self.action, blob_length = struct.unpack(self.SECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.SECURE_PARAMETER_STRUCT_SIZE])
                if len(message.data) < blob_length:
                    raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) security blob', message.raw_data, message)

                self.security_blob = message.data[:blob_length]

class ComTreeConnectAndxRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.55.1
    - [MS-SMB]: 2.2.4.7.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HH'

    def __init__(self, path, service, password = ''):
        self.path = path
        self.service = service
        self.password = password + '\0'

        message.command = SMB_COM_TREE_CONNECT_ANDX

                        0x08 | \
                            ((message.hasExtendedSecurity and 0x0004) or 0x00) | \
                            ((message.tid and message.tid != 0xffff and 0x0001) or 0x00),  # Disconnect tid, if message.tid must be non-zero
                        password_len)

        padding = b''
        if password_len % 2 == 0:
            padding = b'\0'

        # Note that service field is never encoded in UTF-16LE. [MS-CIFS]: 2.2.1.1
        message.data = self.password.encode('UTF-8') + padding + self.path.encode('UTF-16LE') + b'\0\0' + self.service.encode('UTF-8') + b'\0'

class ComTreeConnectAndxResponse(Payload):
    """
    Contains information about the SMB_COM_TREE_CONNECT_ANDX response from the server.

    If the message has no errors, each instance contains the following attributes:
    - optional_support

    - [MS-CIFS]: 2.2.4.55.2
    """

    PAYLOAD_STRUCT_FORMAT = '<BBHH'

        assert message.command == SMB_COM_TREE_CONNECT_ANDX

        if not message.status.hasError:
            if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                raise ProtocolError('Not enough data to decode SMB_COM_TREE_CONNECT_ANDX parameters', message.raw_data, message)

            _, _, _, self.optional_support = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

class ComNTCreateAndxRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.64.1
    - [MS-SMB]: 2.2.4.9.1
    """

    PAYLOAD_STRUCT_FORMAT = '<BHIIIQIIIIIB'

    def __init__(self, filename, flags = 0, root_fid = 0, access_mask = 0, allocation_size = 0, ext_attr = 0,
                 share_access = 0, create_disp = 0, create_options = 0, impersonation = 0, security_flags = 0):
        self.filename = (filename + '\0').encode('UTF-16LE')
        self.flags = flags
        self.root_fid = root_fid
        self.access_mask = access_mask
        self.allocation_size = allocation_size
        self.ext_attr = ext_attr
        self.share_access = share_access
        self.create_disp = create_disp
        self.create_options = create_options
        self.impersonation = impersonation
        self.security_flags = security_flags

        message.command = SMB_COM_NT_CREATE_ANDX

        filename_len = len(self.filename)

                        0x00,                  # reserved
                        filename_len,          # NameLength
                        self.flags,            # Flags
                        self.root_fid,         # RootDirectoryFID
                        self.access_mask,      # DesiredAccess
                        self.allocation_size,  # AllocationSize
                        self.ext_attr,         # ExtFileAttributes
                        self.share_access,     # ShareAccess
                        self.create_disp,      # CreateDisposition
                        self.create_options,   # CreateOptions
                        self.impersonation,    # ImpersonationLevel
                        self.security_flags)   # SecurityFlags

        if (message.HEADER_STRUCT_SIZE + len(message.parameters_data)) % 2 != 0:

        message.data = padding + self.filename

class ComNTCreateAndxResponse(Payload):
    """
    Contains (partial) information about the SMB_COM_NT_CREATE_ANDX response from the server.

    Each instance contains the following attributes after decoding:
    - oplock_level
    - fid

    - [MS-CIFS]: 2.2.4.64.2
    """
    PAYLOAD_STRUCT_FORMAT = '<BBHBH'

        assert message.command == SMB_COM_NT_CREATE_ANDX

                raise ProtocolError('Not enough data to decode SMB_COM_NT_CREATE_ANDX parameters', message.raw_data, message)

            _, _, _, self.oplock_level, self.fid = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

class ComTransactionRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.33.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHHBBHIHHHHHH'

    def __init__(self, max_params_count, max_data_count, max_setup_count,
                 total_params_count = 0, total_data_count = 0,
                 params_bytes = b'', data_bytes = b'', setup_bytes = b'',
                 flags = 0, timeout = 0, name = "\\PIPE\\"):
        self.total_params_count = total_params_count or len(params_bytes)
        self.total_data_count = total_data_count or len(data_bytes)
        self.max_params_count = max_params_count
        self.max_data_count = max_data_count
        self.max_setup_count = max_setup_count
        self.timeout = timeout
        self.params_bytes = params_bytes
        self.data_bytes = data_bytes
        self.setup_bytes = setup_bytes
        self.name = name

        message.command = SMB_COM_TRANSACTION

        name = (self.name + '\0').encode('UTF-16LE')
        name_len = len(name)
        setup_bytes_len = len(self.setup_bytes)
        params_bytes_len = len(self.params_bytes)
        data_bytes_len = len(self.data_bytes)

        padding0 = b''
        offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
        if offset % 2 != 0:
            padding0 = b'\0'
            offset += 1

        offset += name_len  # For the name field
        padding1 = b''
        if offset % 4 != 0:
            padding1 = b'\0'*(4-offset%4)
            offset += (4-offset%4)

        if params_bytes_len > 0:
            params_bytes_offset = offset
            offset += params_bytes_len
        else:
            params_bytes_offset = 0

        padding2 = b''
            padding2 = b'\0'*(4-offset%4)

        if data_bytes_len > 0:
            data_bytes_offset = offset
        else:
            data_bytes_offset = 0

                        self.total_params_count,
                        self.total_data_count,
                        self.max_params_count,
                        self.max_data_count,
                        self.max_setup_count,
                        0x00,           # Reserved1. Must be 0x00
                        self.flags,
                        self.timeout,
                        0x0000,         # Reserved2. Must be 0x0000
                        params_bytes_len,
                        params_bytes_offset,
                        data_bytes_len,
                        data_bytes_offset,
                        int(setup_bytes_len / 2)) + \
            self.setup_bytes

        message.data = padding0 + name + padding1 + self.params_bytes + padding2 + self.data_bytes

class ComTransactionResponse(Payload):
    """
    Contains information about a SMB_COM_TRANSACTION response from the server

    After decoding, each instance contains the following attributes:
    - total_params_count (integer)
    - total_data_count (integer)
    - setup_bytes (string)
    - data_bytes (string)
    - params_bytes (string)

    - [MS-CIFS]: 2.2.4.33.2
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHH'

        assert message.command == SMB_COM_TRANSACTION

                raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)

            self.total_params_count, self.total_data_count, _, \
            params_bytes_len, params_bytes_offset, params_bytes_displ, \
            data_bytes_len, data_bytes_offset, data_bytes_displ, \
            setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

            if setup_count > 0:
                setup_bytes_len = setup_count * 2

                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE + setup_bytes_len:
                    raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)

                self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_bytes_len]
            else:
                self.setup_bytes = ''

            offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count * 2 + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)

            if params_bytes_len > 0:
                self.params_bytes = message.data[params_bytes_offset-offset:params_bytes_offset-offset+params_bytes_len]
            else:
                self.params_bytes = ''

            if data_bytes_len > 0:
                self.data_bytes = message.data[data_bytes_offset-offset:data_bytes_offset-offset+data_bytes_len]
            else:
                self.data_bytes = ''

class ComTransaction2Request(Payload):
    """
    - [MS-CIFS]: 2.2.4.46.1
    """

    PAYLOAD_STRUCT_FORMAT = 'HHHHBBHIHHHHHH'

                 flags = 0, timeout = 0):

        message.command = SMB_COM_TRANSACTION2

        name = b'\0\0'

            offset += 1

        offset += 2  # For the name field

        else:

        else:

class ComTransaction2Response(Payload):
    """
    Contains information about a SMB_COM_TRANSACTION2 response from the server

    - [MS-CIFS]: 2.2.4.46.2
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHBB'

        assert message.command == SMB_COM_TRANSACTION2

                raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION2 parameters', message.raw_data, message)

            setup_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

            else:

            else:

            else:

class ComCloseRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.5.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HI'

    def __init__(self, fid, last_modified_time = 0xFFFFFFFF):
        self.fid = fid
        self.last_modified_time = last_modified_time

        message.command = SMB_COM_CLOSE

        message.parameters_data = struct.pack(self.PAYLOAD_STRUCT_FORMAT, self.fid, self.last_modified_time)
        message.data = b''

class ComOpenAndxRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.41.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HHHHIHIII'

    def __init__(self, filename, access_mode, open_mode, flags = 0x0000, search_attributes = 0, file_attributes = 0, create_time = 0, timeout = 0):
        """
        @param create_time: Epoch time value to indicate the time of creation for this file. If zero, we will automatically assign the current time
        @type create_time: int
        @param timeout: Number of milliseconds to wait for blocked open request before failing
        @type timeout: int
        """
        self.filename = filename
        self.access_mode = access_mode
        self.open_mode = open_mode
        self.search_attributes = search_attributes
        self.file_attributes = file_attributes
        self.create_time = create_time or int(time.time())

        message.command = SMB_COM_OPEN_ANDX

                        self.access_mode,
                        self.search_attributes,
                        self.file_attributes,
                        self.create_time,
                        self.open_mode,
                        0,  # AllocationSize
                        0,  # Timeout (in milli-secs)
                        0)  # Reserved

        message.data = b'\0' + self.filename.encode('UTF-16LE') + b'\0\0'

class ComOpenAndxResponse(Payload):
    """
    Contains information about a SMB_COM_OPEN_ANDX response from the server

    After decoding, each instance will contain the following attributes:
    - fid (integer)
    - file_attributes (integer)
    - last_write_time (long)
    - access_rights (integer)
    - resource_type (integer)
    - open_results (integer)

    - [MS-CIFS]: 2.2.4.41.2
    - [MS-SMB]: 2.2.4.1.2
    """

    PAYLOAD_STRUCT_FORMAT = '<BBHHHIIHHHHHHH'

        assert message.command == SMB_COM_OPEN_ANDX

                raise ProtocolError('Not enough data to decode SMB_COM_OPEN_ANDX parameters', message.raw_data, message)

            _, _, _, self.fid, self.file_attributes, self.last_write_time, _, \
            self.access_rights, self.resource_type, _, self.open_results, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                                                  message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

class ComWriteAndxRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.43.1
    - [MS-SMB]: 2.2.4.3.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HIIHHHHHI'

    def __init__(self, fid, data_bytes, offset, write_mode = 0, timeout = 0):
        """
        @param timeout: Number of milliseconds to wait for blocked write request before failing. Must be zero for writing to regular file
        """
        self.offset = offset
        self.write_mode = write_mode

        message.command = SMB_COM_WRITE_ANDX

        # constant 1 is to account for the pad byte in the message.data
        # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
        data_offset = message.HEADER_STRUCT_SIZE + self.DEFAULT_ANDX_PARAM_SIZE + self.PAYLOAD_STRUCT_SIZE + 1 + 2
        data_len = len(self.data_bytes)

                        self.fid,
                        self.offset & 0xFFFFFFFF,
                        self.write_mode,
                        data_len,   # Remaining
                        0x0000,     # Reserved
                        len(self.data_bytes),  # DataLength
                        data_offset,           # DataOffset
                        self.offset >> 32)     # OffsetHigh field defined in [MS-SMB]: 2.2.4.3.1

        message.data = b'\0' + self.data_bytes

class ComWriteAndxResponse(Payload):
    """
    - [MS-CIFS]: 2.2.4.43.2
    - [MS-SMB]: 2.2.4.3.2
    """

    PAYLOAD_STRUCT_FORMAT = '<BBHHHHH'  # We follow the SMB_COM_WRITEX_ANDX server extensions in [MS-SMB]: 2.2.4.3.2

        assert message.command == SMB_COM_WRITE_ANDX

                raise ProtocolError('Not enough data to decode SMB_COM_WRITE_ANDX parameters', message.raw_data, message)

            _, _, _, count, self.available, high_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
            self.count = (count & 0xFFFF) | (high_count << 16)

class ComReadAndxRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.42.1
    - [MS-SMB]: 2.2.4.2.1
    """

    PAYLOAD_STRUCT_FORMAT = '<HIHHIHI'

    def __init__(self, fid, offset, max_return_bytes_count, min_return_bytes_count, timeout = 0, remaining = 0):
        """
        @param timeout: If reading from a regular file, this parameter must be 0.
        """
        self.remaining = remaining
        self.max_return_bytes_count = max_return_bytes_count
        self.min_return_bytes_count = min_return_bytes_count

        message.command = SMB_COM_READ_ANDX

                        self.fid,
                        self.max_return_bytes_count,
                        self.min_return_bytes_count,
                        self.timeout or (self.max_return_bytes_count >> 32),  # Note that in [MS-SMB]: 2.2.4.2.1, this field can also act as MaxCountHigh field
                        self.remaining, # In [MS-CIFS]: 2.2.4.42.1, this field must be set to 0x0000
                        self.offset >> 32)

class ComReadAndxResponse(Payload):
    """
    - [MS-CIFS]: 2.2.4.42.2
    - [MS-SMB]: 2.2.4.2.2
    """

    PAYLOAD_STRUCT_FORMAT = '<BBHHHHHHHHHHH'

        assert message.command == SMB_COM_READ_ANDX

                raise ProtocolError('Not enough data to decode SMB_COM_READ_ANDX parameters', message.raw_data, message)

            _, _, _, _, _, _, self.data_length, data_offset, _, _, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                                           message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

            offset = data_offset - message.HEADER_STRUCT_SIZE - self.PAYLOAD_STRUCT_SIZE - 2  # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
            self.data = message.data[offset:offset+self.data_length]
            assert len(self.data) == self.data_length

class ComDeleteRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.7.1
    """

    def __init__(self, filename_pattern, search_attributes = 0):
        self.filename_pattern = filename_pattern

        message.command = SMB_COM_DELETE

        message.parameters_data = struct.pack('<H', self.search_attributes)
        message.data = b'\x04' + self.filename_pattern.encode('UTF-16LE') + b'\0\0'

class ComCreateDirectoryRequest(Payload):
    """
    Although this command has been marked deprecated in [MS-CIFS], we continue to use it for its simplicity
    as compared to its replacement TRANS2_CREATE_DIRECTORY sub-command [MS-CIFS]: 2.2.6.14

    - [MS-CIFS]: 2.2.4.1.1
    """

    def __init__(self, path):

        message.command = SMB_COM_CREATE_DIRECTORY

        message.data = b'\x04' + self.path.encode('UTF-16LE') + b'\0\0'

class ComDeleteDirectoryRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.2.1
    """

        message.command = SMB_COM_DELETE_DIRECTORY

class ComRenameRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.8.1
    """

    def __init__(self, old_path, new_path, search_attributes = 0):
        self.old_path = old_path
        self.new_path = new_path

        message.command = SMB_COM_RENAME

        message.data = b'\x04' + self.old_path.encode('UTF-16LE') + b'\x00\x00\x04\x00' + self.new_path.encode('UTF-16LE') + b'\x00\x00'

class ComEchoRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.39.1
    """

    def __init__(self, echo_data = b'', echo_count = 1):
        self.echo_count = echo_count
        self.echo_data = echo_data

        message.command = SMB_COM_ECHO
        message.tid = 0xFFFF

        message.parameters_data = struct.pack('<H', self.echo_count)
        message.data = self.echo_data

class ComEchoResponse(Payload):
    """
    - [MS-CIFS]: 2.2.4.39.2
    """

        self.sequence_number = struct.unpack('<H', message.parameters_data[:2])[0]
        self.data = message.data

class ComNTTransactRequest(Payload):
    """
    - [MS-CIFS]: 2.2.4.62.1
    """
    PAYLOAD_STRUCT_FORMAT = '<BHIIIIIIIIBH'

    def __init__(self, function, max_params_count, max_data_count, max_setup_count,
                 params_bytes = b'', setup_bytes = b'', data_bytes = b''):
        self.function = function

        message.command = SMB_COM_NT_TRANSACT

            padding0 = b'\0'*(4-offset%4)

        else:

        offset += params_bytes_len

        else:

                        int(setup_bytes_len / 2),
                        self.function) + \

        message.data = padding0 + self.params_bytes + padding1 + self.data_bytes

class ComNTTransactResponse(Payload):
    """
    Contains information about a SMB_COM_NT_TRANSACT response from the server

    - [MS-CIFS]: 2.2.4.62.2
    """
    PAYLOAD_STRUCT_FORMAT = '<3sIIIIIIIIBH'

        assert message.command == SMB_COM_NT_TRANSACT

            _, self.total_params_count, self.total_data_count, \
            params_count, params_offset, params_displ, \
            data_count, data_offset, data_displ, setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                             message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

            self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_count*2]

            if params_count > 0:
                params_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
                self.params_bytes = message.data[params_offset:params_offset+params_count]
            else:
                self.params_bytes = b''

            if data_count > 0:
                data_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
                self.data_bytes = message.data[data_offset:data_offset+data_count]
            else:
                self.data_bytes = b''

"""
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
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

        """
        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.
        #: 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

        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)

    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_SYSTEM_AUDIT_CALLBACK,
            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.
        #: List of :class:`ACE` instances.
        self.aces = aces

        return "ACL(%r)" % (self.aces,)

        revision = None
        aces = []

        header, remaining = data[:header_size], data[header_size:]
        revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)

        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.
        #: 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

        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)