diff --git a/CHANGELOG b/CHANGELOG index e52126b..aed051f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,53 @@ +pysmb-1.1.26, 5 Jan 2019 +======================== +- Prevents OperationError from being raised when listPath() operation does not + return any matching file results. +- SMBConnection is now a context manager #122. + +pysmb-1.1.25, 28 July 2018 +======================== +- Fix buggy support for search parameter in listPath() method. Add + SMB_FILE_ATTRIBUTE_INCL_NORMAL bit constant to include 'normal' files with + other file types in the returned result. From now on, 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. listPath() method will now include 'normal' files + using the default search parameter. +- Add isNormal property to SharedFile class to support test if the file is a + 'normal' file (according to pysmb definition of 'normal' file). + +pysmb-1.1.24, 19 July 2018 +======================== +- Improve listPath implementation for SMB1 +- Support for STATUS_PENDING responses across all SMB2 operations. + +pysmb-1.1.23, 5 May 2018 +======================== +- Fix bug in listShares() method which fails when the remote server has many shares. +- Improve echo() method to test and fail if the provided data to echo is not a bytes object. +- Fix bug in listPath() method where the path to query is not properly terminated. + +pysmb-1.1.22, 17 Sep 2017 +======================== +- Fix bug in getAttributes() method which should return only the filename + instead of the entire path for the filename property for the return result. + +pysmb-1.1.21, 9 Sep 2017 +======================== +- Fix bug where timestamp values for SMB1 getAttributes() response are not + converted properly from FILETIME to epoch time values. + +pysmb-1.1.20, 13 Aug 2017 +========================= +- Add getSecurity() method to support security descriptors query via SMB2 +- Improve retrieveFile() and retrieveFileFromOffset() methods to allow file + retrievals over SMB2 even when the file is being locked on the server. +- Silently discards NMB SESSION_KEEPALIVE packets instead of raising warnings. +- SMB sessionID will be sent in ECHO requests to conform to SMB2 specs. +- Fix type errors for MD4 functions in python3. + pysmb-1.1.19, 13 Nov 2016 -======================== +========================= - Ignore STATUS_PENDING during delete and file store operations pysmb-1.1.18, 9 Apr 2016 diff --git a/LICENSE b/LICENSE index d183506..7eaccfa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (C) 2001-2015 Michael Teo +Copyright (C) 2001-2019 Michael Teo This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b22628d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include LICENSE +include CHANGELOG +recursive-include python2 * +recursive-exclude python2 *.pyc +recursive-exclude python2 *~ +recursive-include python3 * +recursive-exclude python3 *.pyc +recursive-exclude python3 *~ +recursive-include sphinx * +recursive-include docs * +recursive-exclude docs *.zip diff --git a/PKG-INFO b/PKG-INFO index ba886a1..9f8779d 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pysmb -Version: 1.1.19 +Version: 1.1.26 Summary: pysmb is an experimental SMB/CIFS library written in Python to support file sharing between Windows and Linux machines Home-page: https://miketeo.net/projects/pysmb Author: Michael Teo @@ -16,9 +16,6 @@ Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.4 -Classifier: Programming Language :: Python :: 2.5 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Communications :: File Sharing diff --git a/docs/doctrees/api/nmb_NetBIOS.doctree b/docs/doctrees/api/nmb_NetBIOS.doctree index 9b279b3..970596a 100644 Binary files a/docs/doctrees/api/nmb_NetBIOS.doctree and b/docs/doctrees/api/nmb_NetBIOS.doctree differ diff --git a/docs/doctrees/api/smb_SMBConnection.doctree b/docs/doctrees/api/smb_SMBConnection.doctree index 383d6bf..063d562 100644 Binary files a/docs/doctrees/api/smb_SMBConnection.doctree and b/docs/doctrees/api/smb_SMBConnection.doctree differ diff --git a/docs/doctrees/api/smb_SMBHandler.doctree b/docs/doctrees/api/smb_SMBHandler.doctree index 2fb06a4..5b414de 100644 Binary files a/docs/doctrees/api/smb_SMBHandler.doctree and b/docs/doctrees/api/smb_SMBHandler.doctree differ diff --git a/docs/doctrees/api/smb_SMBProtocolFactory.doctree b/docs/doctrees/api/smb_SMBProtocolFactory.doctree index d424daa..1e5933d 100644 Binary files a/docs/doctrees/api/smb_SMBProtocolFactory.doctree and b/docs/doctrees/api/smb_SMBProtocolFactory.doctree differ diff --git a/docs/doctrees/api/smb_SharedDevice.doctree b/docs/doctrees/api/smb_SharedDevice.doctree index eb0fa96..d2b2c34 100644 Binary files a/docs/doctrees/api/smb_SharedDevice.doctree and b/docs/doctrees/api/smb_SharedDevice.doctree differ diff --git a/docs/doctrees/api/smb_SharedFile.doctree b/docs/doctrees/api/smb_SharedFile.doctree index faa32e5..d4d3b8a 100644 Binary files a/docs/doctrees/api/smb_SharedFile.doctree and b/docs/doctrees/api/smb_SharedFile.doctree differ diff --git a/docs/doctrees/api/smb_exceptions.doctree b/docs/doctrees/api/smb_exceptions.doctree index aec1229..31eff13 100644 Binary files a/docs/doctrees/api/smb_exceptions.doctree and b/docs/doctrees/api/smb_exceptions.doctree differ diff --git a/docs/doctrees/api/smb_security_descriptors.doctree b/docs/doctrees/api/smb_security_descriptors.doctree new file mode 100644 index 0000000..734f4f6 Binary files /dev/null and b/docs/doctrees/api/smb_security_descriptors.doctree differ diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index 987c4bf..843712b 100644 Binary files a/docs/doctrees/environment.pickle and b/docs/doctrees/environment.pickle differ diff --git a/docs/doctrees/extending.doctree b/docs/doctrees/extending.doctree index dc0b3fc..4031973 100644 Binary files a/docs/doctrees/extending.doctree and b/docs/doctrees/extending.doctree differ diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree index 807f40d..abeb85f 100644 Binary files a/docs/doctrees/index.doctree and b/docs/doctrees/index.doctree differ diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index bf80f8a..69caef7 100644 --- a/docs/html/.buildinfo +++ b/docs/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 8ec856095809db2990831edf01ebc5a4 +config: 24e4dd110b51998b070280bf87a2d415 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_modules/index.html b/docs/html/_modules/index.html index c518f1e..972635b 100644 --- a/docs/html/_modules/index.html +++ b/docs/html/_modules/index.html @@ -6,7 +6,7 @@ - Overview: module code — pysmb 1.1.18 documentation + Overview: module code — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + diff --git a/docs/html/_modules/nmb/NetBIOS.html b/docs/html/_modules/nmb/NetBIOS.html index 82bdf75..680ecec 100644 --- a/docs/html/_modules/nmb/NetBIOS.html +++ b/docs/html/_modules/nmb/NetBIOS.html @@ -6,7 +6,7 @@ - nmb.NetBIOS — pysmb 1.1.18 documentation + nmb.NetBIOS — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -218,12 +221,15 @@
  • index
  • - +
  • + modules |
  • + diff --git a/docs/html/_modules/nmb/NetBIOSProtocol.html b/docs/html/_modules/nmb/NetBIOSProtocol.html index 50dade2..05991f5 100644 --- a/docs/html/_modules/nmb/NetBIOSProtocol.html +++ b/docs/html/_modules/nmb/NetBIOSProtocol.html @@ -6,7 +6,7 @@ - nmb.NetBIOSProtocol — pysmb 1.1.18 documentation + nmb.NetBIOSProtocol — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -210,12 +213,15 @@
  • index
  • - +
  • + modules |
  • + diff --git a/docs/html/_modules/smb/SMBConnection.html b/docs/html/_modules/smb/SMBConnection.html deleted file mode 100644 index 1df1ca8..0000000 --- a/docs/html/_modules/smb/SMBConnection.html +++ /dev/null @@ -1,668 +0,0 @@ - - - - - - - - smb.SMBConnection — pysmb 1.1.18 documentation - - - - - - - - - - - - - - - -
    -
    -
    -
    - -

    Source code for smb.SMBConnection

    -
    -import os, logging, select, socket, struct, errno
    -from smb_constants import *
    -from smb_structs import *
    -from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
    -
    -
    -
    [docs]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 - -
    [docs] 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. - File operations can only be proceeded after the connection has been authenticated successfully. - - Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication. - - The default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139. - Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445. - - :param string my_name: The local NetBIOS machine name that will identify where this connection is originating from. - You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+`` - :param string remote_name: The NetBIOS machine name of the remote server. - On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties". - This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected. - :param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string. - :param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication. - The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured. - Hence, we can only "guess" or try both algorithms. - On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2. - :param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*. - If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing. - If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing. - If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing. - :param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication. - The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139). - """ - SMB.__init__(self, username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp) - self.sock = None - self.auth_result = None - self.is_busy = False - self.is_direct_tcp = is_direct_tcp
    - - # - # SMB (and its superclass) Methods - # - - def onAuthOK(self): - self.auth_result = True - - def onAuthFailed(self): - self.auth_result = False - - def write(self, data): - assert self.sock - data_len = len(data) - total_sent = 0 - while total_sent < data_len: - sent = self.sock.send(data[total_sent:]) - if sent == 0: - raise NotConnectedError('Server disconnected') - total_sent = total_sent + sent - - # - # 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 - # - -
    [docs] def connect(self, ip, port = 139, sock_family = socket.AF_INET, timeout = 60): - """ - Establish the SMB connection to the remote SMB/CIFS server. - - You must call this method before attempting any of the file operations with the remote server. - This method will block until the SMB connection has attempted at least one authentication. - - :return: A boolean value indicating the result of the authentication atttempt: True if authentication is successful; False, if otherwise. - """ - if self.sock: - self.sock.close() - - self.auth_result = None - self.sock = socket.socket(sock_family) - self.sock.settimeout(timeout) - self.sock.connect(( ip, port )) - - self.is_busy = True - try: - if not self.is_direct_tcp: - self.requestNMBSession() - else: - self.onNMBSessionOK() - while self.auth_result is None: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return self.auth_result
    - -
    [docs] def close(self): - """ - Terminate the SMB connection (if it has been started) and release any sources held by the underlying socket. - """ - if self.sock: - self.sock.close() - self.sock = None
    - -
    [docs] def listShares(self, timeout = 30): - """ - Retrieve a list of shared resources on remote server. - - :return: A list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances describing the shared resource - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(entries): - self.is_busy = False - results.extend(entries) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._listShares(cb, eb, timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results
    - -
    [docs] 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, - pattern = '*', timeout = 30): - """ - Retrieve a directory listing of files/folders at *path* - - :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). - The default *search* value will query for all read-only, hidden, system, archive files and directories. - :param string/unicode pattern: the filter to apply to the results before returning to the client. - :return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances. - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(entries): - self.is_busy = False - results.extend(entries) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._listPath(service_name, path, cb, eb, search = search, pattern = pattern, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results
    - -
    [docs] def listSnapshots(self, service_name, path, timeout = 30): - """ - Retrieve a list of available snapshots (shadow copies) for *path*. - - Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots - :return: A list of python *datetime.DateTime* instances in GMT/UTC time zone - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(entries): - self.is_busy = False - results.extend(entries) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._listSnapshots(service_name, path, cb, eb, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results
    - -
    [docs] def getAttributes(self, service_name, path, timeout = 30): - """ - Retrieve information about the file at *path* on the *service_name*. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :return: A :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file. - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(info): - self.is_busy = False - results.append(info) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._getAttributes(service_name, path, cb, eb, timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results[0]
    - -
    [docs] def retrieveFile(self, service_name, path, file_obj, timeout = 30): - """ - Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*. - - Use *retrieveFileFromOffset()* method if you wish to specify the offset to read from the remote *path* and/or the number of bytes to write to the *file_obj*. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. - :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, 0L, -1L, timeout)
    - -
    [docs] def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30): - """ - Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* up to *max_length* number of bytes. - :param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value. - :param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF. - If zero, the method returns immediately after the file is opened successfully for reading. - :return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ). - The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py) - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(r): - self.is_busy = False - results.append(r[1:]) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._retrieveFileFromOffset(service_name, path, file_obj, cb, eb, offset, max_length, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results[0]
    - -
    [docs] def storeFile(self, service_name, path, file_obj, timeout = 30): - """ - Store the contents of the *file_obj* at *path* on the *service_name*. - If the file already exists on the remote server, it will be truncated and overwritten. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten. - If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. - :return: Number of bytes uploaded - """ - return self.storeFileFromOffset(service_name, path, file_obj, 0L, True, timeout)
    - -
    [docs] def storeFileFromOffset(self, service_name, path, file_obj, offset = 0L, truncate = False, timeout = 30): - """ - Store the contents of the *file_obj* at *path* on the *service_name*. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. - If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. - :param offset: Long integer value which specifies the offset in the remote server to start writing. First byte of the file is 0. - :param truncate: Boolean value. If True and the file exists on the remote server, it will be truncated first before writing. Default is False. - :return: the file position where the next byte will be written. - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(r): - self.is_busy = False - results.append(r[1]) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._storeFileFromOffset(service_name, path, file_obj, cb, eb, offset, truncate = truncate, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results[0]
    - -
    [docs] def deleteFiles(self, service_name, path_file_pattern, 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. - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. - Wildcards may be used in th filename component of the path. - If your path/filename contains non-English characters, you must pass in an unicode string. - :return: None - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - def cb(r): - self.is_busy = False - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._deleteFiles(service_name, path_file_pattern, cb, eb, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False
    - -
    [docs] def resetFileAttributes(self, service_name, path_file_pattern, 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. - Note: this function is currently only implemented for SMB2! Technically, it sets the FILE_ATTRIBUTE_NORMAL flag, therefore clearing all other flags. (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information) - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. - Wildcards may be used in the filename component of the path. - If your path/filename contains non-English characters, you must pass in an unicode string. - :return: None - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - def cb(r): - self.is_busy = False - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._resetFileAttributes(service_name, path_file_pattern, cb, eb, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False
    - -
    [docs] def createDirectory(self, service_name, path, timeout = 30): - """ - Creates a new directory *path* on the *service_name*. - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path: The path of the new folder (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - :return: None - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - def cb(r): - self.is_busy = False - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._createDirectory(service_name, path, cb, eb, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False
    - -
    [docs] def deleteDirectory(self, service_name, path, timeout = 30): - """ - Delete the empty folder at *path* on *service_name* - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - :return: None - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - def cb(r): - self.is_busy = False - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._deleteDirectory(service_name, path, cb, eb, timeout = timeout) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False
    - -
    [docs] def rename(self, service_name, old_path, new_path, timeout = 30): - """ - Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders - - *old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - - :param string/unicode service_name: Contains the name of the shared folder. - :return: None - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - def cb(r): - self.is_busy = False - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._rename(service_name, old_path, new_path, cb, eb) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False
    - -
    [docs] 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 string data: Data to send to the remote server. - :return: The *data* parameter - """ - if not self.sock: - raise NotConnectedError('Not connected to server') - - results = [ ] - - def cb(r): - self.is_busy = False - results.append(r) - - def eb(failure): - self.is_busy = False - raise failure - - self.is_busy = True - try: - self._echo(data, cb, eb) - while self.is_busy: - self._pollForNetBIOSPacket(timeout) - finally: - self.is_busy = False - - return results[0]
    - - # - # Protected Methods - # - - def _pollForNetBIOSPacket(self, timeout): - expiry_time = time.time() + timeout - read_len = 4 - data = '' - - while read_len > 0: - try: - if expiry_time < time.time(): - raise SMBTimeout - - ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout) - if not ready: - raise SMBTimeout - - d = self.sock.recv(read_len) - if len(d) == 0: - raise NotConnectedError - - data = data + d - read_len -= len(d) - except select.error, ex: - if isinstance(ex, types.TupleType): - if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: - raise ex - else: - raise ex - - type_, flags, length = struct.unpack('>BBH', data) - if flags & 0x01: - length = length | 0x10000 - - read_len = length - while read_len > 0: - try: - if expiry_time < time.time(): - raise SMBTimeout - - ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout) - if not ready: - raise SMBTimeout - - d = self.sock.recv(read_len) - if len(d) == 0: - raise NotConnectedError - - data = data + d - read_len -= len(d) - except select.error, ex: - if isinstance(ex, types.TupleType): - if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: - raise ex - else: - raise ex - - self.feedData(data)
    -
    - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/docs/html/_modules/smb/SMBProtocol.html b/docs/html/_modules/smb/SMBProtocol.html deleted file mode 100644 index 20cbc73..0000000 --- a/docs/html/_modules/smb/SMBProtocol.html +++ /dev/null @@ -1,487 +0,0 @@ - - - - - - - - smb.SMBProtocol — pysmb 1.1.18 documentation - - - - - - - - - - - - - - - -
    -
    -
    -
    - -

    Source code for smb.SMBProtocol

    -
    -import os, logging, time
    -from twisted.internet import reactor, defer
    -from twisted.internet.protocol import ClientFactory, Protocol
    -from smb_constants import *
    -from smb_structs import *
    -from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
    -
    -
    -__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
    -
    -
    -class SMBProtocol(Protocol, SMB):
    -
    -    log = logging.getLogger('SMB.SMBProtocol')
    -
    -    #
    -    # Protocol Methods
    -    #
    -
    -    def connectionMade(self):
    -        self.factory.instance = self
    -        if not self.is_direct_tcp:
    -            self.requestNMBSession()
    -        else:
    -            self.onNMBSessionOK()
    -
    -    def connectionLost(self, reason):
    -        if self.factory.instance == self:
    -            self.instance = None
    -
    -    def dataReceived(self, data):
    -        self.feedData(data)
    -
    -    #
    -    # SMB (and its superclass) Methods
    -    #
    -
    -    def write(self, data):
    -        self.transport.write(data)
    -
    -    def onAuthOK(self):
    -        if self.factory.instance == self:
    -            self.factory.onAuthOK()
    -            reactor.callLater(1, self._cleanupPendingRequests)
    -
    -    def onAuthFailed(self):
    -        if self.factory.instance == self:
    -            self.factory.onAuthFailed()
    -
    -    def onNMBSessionFailed(self):
    -        self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
    -
    -    #
    -    # Protected Methods
    -    #
    -
    -    def _cleanupPendingRequests(self):
    -        if self.factory.instance == self:
    -            now = time.time()
    -            to_remove = []
    -            for mid, r in self.pending_requests.iteritems():
    -                if r.expiry_time < now:
    -                    try:
    -                        r.errback(SMBTimeout())
    -                    except Exception: pass
    -                    to_remove.append(mid)
    -
    -            for mid in to_remove:
    -                del self.pending_requests[mid]
    -
    -            reactor.callLater(1, self._cleanupPendingRequests)
    -
    -
    -
    [docs]class SMBProtocolFactory(ClientFactory): - - protocol = SMBProtocol - log = logging.getLogger('SMB.SMBFactory') - - #: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing. - SIGN_NEVER = 0 - #: SMB messages will be signed when remote server supports signing but not requires signing. - SIGN_WHEN_SUPPORTED = 1 - #: SMB messages will only be signed when remote server requires signing. - SIGN_WHEN_REQUIRED = 2 - -
    [docs] def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False): - """ - Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server. - Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139. - Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445. - - *username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server. - File operations can only be proceeded after the connection has been authenticated successfully. - - :param string my_name: The local NetBIOS machine name that will identify where this connection is originating from. - You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``. - :param string remote_name: The NetBIOS machine name of the remote server. - On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties". - This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected. - :param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string. - :param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication. - The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured. - Hence, we can only "guess" or try both algorithms. - On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2. - :param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*. - If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing. - If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing. - If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing. - :param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication. - The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139). - """ - self.username = username - self.password = password - self.my_name = my_name - self.remote_name = remote_name - self.domain = domain - self.use_ntlm_v2 = use_ntlm_v2 - self.sign_options = sign_options - self.is_direct_tcp = is_direct_tcp - self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
    - - # - # Public Property - # - - @property - def isReady(self): - """A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations.""" - return bool(self.instance and self.instance.has_authenticated) - - @property - def isUsingSMB2(self): - """A convenient property to return True if the underlying SMB connection is using SMB2 protocol.""" - return self.instance and self.instance.is_using_smb2 - - # - # Public Methods for Callbacks - # - -
    [docs] def onAuthOK(self): - """ - Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling. - This method will be called when the server has replied that the SMB connection has been successfully authenticated. - File operations can proceed when this method has been called. - """ - pass
    - -
    [docs] def onAuthFailed(self): - """ - Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling. - This method will be called when the server has replied that the SMB connection has been successfully authenticated. - - If you want to retry authenticating from this method, - 1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``) - 2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag. - 3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection - """ - pass
    - - # - # Public Methods - # - -
    [docs] def listShares(self, timeout = 30): - """ - Retrieve a list of shared resources on remote server. - - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._listShares(d.callback, d.errback, timeout) - return d
    - -
    [docs] 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, - pattern = '*', timeout = 30): - """ - Retrieve a directory listing of files/folders at *path* - - :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). - The default *search* value will query for all read-only, hidden, system, archive files and directories. - :param string/unicode pattern: the filter to apply to the results before returning to the client. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout) - return d
    - -
    [docs] def listSnapshots(self, service_name, path, timeout = 30): - """ - Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*. - - Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime* - instances in GMT/UTC time zone - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout) - return d
    - -
    [docs] def getAttributes(self, service_name, path, timeout = 30): - """ - Retrieve information about the file at *path* on the *service_name*. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout) - return d
    - -
    [docs] def retrieveFile(self, service_name, path, file_obj, timeout = 30): - """ - Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*. - - Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*. - - The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size - of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes). - The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback. - :param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ). - The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py) - """ - return self.retrieveFileFromOffset(service_name, path, file_obj, 0L, -1L, timeout)
    - -
    [docs] def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30): - """ - Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*. - - The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size - of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes). - The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback. - :param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. - :param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value. - :param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF. - If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ). - The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py) - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout) - return d
    - -
    [docs] def storeFile(self, service_name, path, file_obj, timeout = 30): - """ - Store the contents of the *file_obj* at *path* on the *service_name*. - - The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size - of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes). - The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged - by the remote SMB/CIFS server. - - :param string/unicode service_name: the name of the shared folder for the *path* - :param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten. - If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback. - :param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ). - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout) - return d
    - -
    [docs] def deleteFiles(self, service_name, path_file_pattern, 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. - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. - Wildcards may be used in th filename component of the path. - If your path/filename contains non-English characters, you must pass in an unicode string. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._deleteFiles(service_name, path_file_pattern, d.callback, d.errback, timeout = timeout) - return d
    - -
    [docs] def createDirectory(self, service_name, path): - """ - Creates a new directory *path* on the *service_name*. - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path: The path of the new folder (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._createDirectory(service_name, path, d.callback, d.errback) - return d
    - -
    [docs] def deleteDirectory(self, service_name, path): - """ - Delete the empty folder at *path* on *service_name* - - :param string/unicode service_name: Contains the name of the shared folder. - :param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._deleteDirectory(service_name, path, d.callback, d.errback) - return d
    - -
    [docs] def rename(self, service_name, old_path, new_path): - """ - Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders - - *old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder. - If the path contains non-English characters, an unicode string must be used to pass in the path. - - :param string/unicode service_name: Contains the name of the shared folder. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ). - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._rename(service_name, old_path, new_path, d.callback, d.errback) - return d
    - -
    [docs] 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 string data: Data to send to the remote server. - :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. - :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter. - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - d = defer.Deferred() - self.instance._echo(data, d.callback, d.errback, timeout) - return d
    - -
    [docs] def closeConnection(self): - """ - Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns. - - :return: None - """ - if not self.instance: - raise NotConnectedError('Not connected to server') - - self.instance.transport.loseConnection()
    - - # - # ClientFactory methods - # (Do not touch these unless you know what you are doing) - # - - def buildProtocol(self, addr): - p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp) - p.factory = self - return p
    -
    - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/docs/html/_modules/smb/base.html b/docs/html/_modules/smb/base.html deleted file mode 100644 index 9b94c8d..0000000 --- a/docs/html/_modules/smb/base.html +++ /dev/null @@ -1,2746 +0,0 @@ - - - - - - - - smb.base — pysmb 1.1.18 documentation - - - - - - - - - - - - - - - -
    -
    -
    -
    - -

    Source code for smb.base

    -
    -import logging, binascii, time, hmac
    -from datetime import datetime
    -from smb_constants import *
    -from smb2_constants import *
    -from smb_structs import *
    -from smb2_structs import *
    -from nmb.base import NMBSession
    -from utils import convertFILETIMEtoEpoch
    -import ntlm, securityblob
    -
    -try:
    -    import hashlib
    -    sha256 = hashlib.sha256
    -except ImportError:
    -    from utils import sha256
    -
    -
    -
    [docs]class NotReadyError(Exception): - """Raised when SMB connection is not ready (i.e. not authenticated or authentication failed)""" - pass
    - -
    [docs]class NotConnectedError(Exception): - """Raised when underlying SMB connection has been disconnected or not connected yet""" - pass
    - -
    [docs]class SMBTimeout(Exception): - """Raised when a timeout has occurred while waiting for a response or for a SMB/CIFS operation to complete.""" - pass
    - - -def _convert_to_unicode(string): - if not isinstance(string, unicode): - string = unicode(string, "utf-8") - return string - - -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') - - SIGN_NEVER = 0 - SIGN_WHEN_SUPPORTED = 1 - 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): - NMBSession.__init__(self, my_name, remote_name, is_direct_tcp = is_direct_tcp) - self.username = _convert_to_unicode(username) - self.password = _convert_to_unicode(password) - self.domain = _convert_to_unicode(domain) - self.sign_options = sign_options - self.is_direct_tcp = is_direct_tcp - 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.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') - - - # - # NMBSession Methods - # - - def onNMBSessionOK(self): - self._sendSMBMessage(SMBMessage(ComNegotiateRequest())) - - def onNMBSessionFailed(self): - pass - - def onNMBSessionMessage(self, flags, data): - while True: - 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() - i = self.smb_message.decode(data) - - 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 - # - - def onAuthOK(self): - pass - - def onAuthFailed(self): - pass - - # - # Protected Methods - # - - 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._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._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 and smb_message.command != SMB2_COM_ECHO: - 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: - smb_message.raw_data = smb_message.encode() - 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: - if message.status == 0: - 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!') - 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, ex: - raise ProtocolError(str(ex), message.raw_data, message) - elif message.status == 0xc0000016: # STATUS_MORE_PROCESSING_REQUIRED - self.session_id = message.session_id - 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 ), ex: - raise ProtocolError(str(ex), message.raw_data, message) - elif message.status == 0xc000006d: # STATUS_LOGON_FAILURE - self.has_authenticated = False - self.log.info('Authentication (on SMB2) failed. Please check username and password.') - self.onAuthFailed() - else: - raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status, - message.raw_data, message) - - 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 = ntlm.generateAuthenticateMessage(server_flags, - nt_challenge_response, - lm_challenge_response, - session_key, - self.username, - self.domain) - - 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) - self._sendSMBMessage(SMB2Message(SMB2SessionSetupRequest(blob))) - - 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 - - if self.is_signing_active: - self.log.info("SMB signing activated. All SMB messages will be signed.") - self.signing_session_key = (session_key + '\0'*16)[:16] - 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') - - expiry_time = time.time() + timeout - 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) - 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("""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(' ', '')) + \ - struct.pack('<I', call_id) + \ - binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0)) - m.tid = create_message.tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, 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)) - m.tid = trans_message.tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs['fid']) - messages_history.append(m) - else: - closeFid(trans_message.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: - call_id = self._getNextRPCCallID() - - padding = '' - remote_name = '\\\\' + self.remote_name - server_len = len(remote_name) + 1 - server_bytes_len = server_len * 2 - if server_len % 2 != 0: - padding = '\0\0' - server_bytes_len += 2 - - # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC. - # 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("""05 00 00 03 10 00 00 00""".replace(' ', '')) + \ - struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \ - binascii.unhexlify("""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(' ', '')) + \ - struct.pack('<III', server_len, 0, server_len) + \ - (remote_name + '\0').encode('UTF-16LE') + padding + \ - binascii.unhexlify(""" -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 -""".replace(' ', '').replace('\n', '')) - m = SMB2Message(SMB2IoctlRequest(kwargs['fid'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes)) - m.tid = read_message.tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) - messages_history.append(m) - else: - closeFid(read_message.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 ord(data_bytes[3]) & 0x02 == 0: - sendReadRequest(result_message.tid, kwargs['fid'], data_bytes) - else: - decodeResults(result_message.tid, kwargs['fid'], data_bytes) - elif result_message.status == 0x0103: # STATUS_PENDING - self.pending_requests[result_message.mid] = _PendingRequest(result_message.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) - else: - closeFid(result_message.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 - - for i in range(0, shares_count): - max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12]) - offset += 12 - results[i].name = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE') - - if length % 2 != 0: - offset += (length * 2 + 2) - else: - offset += (length * 2) - - max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12]) - offset += 12 - results[i].comments = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE') - - if length % 2 != 0: - offset += (length * 2 + 2) - else: - offset += (length * 2) - - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, - fid = fid, data_bytes = data_bytes) - - def readCB(read_message, **kwargs): - messages_history.append(read_message) - if read_message.status == 0: - data_len = read_message.payload.data_length - data_bytes = read_message.payload.data - - if ord(data_bytes[3]) & 0x02 == 0: - sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24]) - else: - decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24]) - else: - closeFid(read_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) - - def closeFid(tid, fid, results = None, error = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error) - messages_history.append(m) - - 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 not self.connected_trees.has_key(path): - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path) - messages_history.append(m) - else: - connectSrvSvc(self.connected_trees[path]) - - def _listPath_SMB2(self, service_name, path, callback, errback, search, pattern, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - results = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_DIRECTORY_FILE, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - sendQuery(create_message.tid, create_message.payload.fid, '') - 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 = 0x03, # FileBothDirectoryInformation - flags = 0, - output_buf_len = self.max_transact_size)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, fid = fid, data_buf = data_buf) - messages_history.append(m) - - 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(query_message.tid, kwargs['fid'], data_buf) - elif query_message.status == 0x80000006L: # STATUS_NO_MORE_FILES - closeFid(query_message.tid, kwargs['fid'], results = results) - else: - closeFid(query_message.tid, kwargs['fid'], error = query_message.status) - - def decodeQueryStruct(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' - 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 = struct.unpack(info_format, data_bytes[offset:offset+info_size]) - - offset2 = offset + info_size - if offset2 + filename_length > data_length: - return data_bytes[offset:] - - filename = data_bytes[offset2:offset2+filename_length].decode('UTF-16LE') - short_name = short_name.decode('UTF-16LE') - 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)) - - if next_offset: - offset += next_offset - else: - break - return '' - - def closeFid(tid, fid, results = None, error = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['results'] is not None: - callback(kwargs['results']) - elif kwargs['error'] is not None: - errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _getAttributes_SMB2(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = 0, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - p = create_message.payload - info = SharedFile(p.create_time, p.lastaccess_time, p.lastwrite_time, p.change_time, - p.file_size, p.allocation_size, p.file_attributes, - unicode(path), unicode(path)) - closeFid(create_message.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 = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, info = info, error = error) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['info'] is not None: - callback(kwargs['info']) - elif kwargs['error'] is not None: - errback(OperationFailure('Failed to get attributes for %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to get attributes for %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _retrieveFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30): - return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0L, -1L, timeout) - - def _retrieveFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - results = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE, - share_access = FILE_SHARE_READ, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - m = SMB2Message(SMB2QueryInfoRequest(create_message.payload.fid, - flags = 0, - additional_info = 0, - info_type = SMB2_INFO_FILE, - file_info_class = 0x16, # FileStreamInformation [MS-FSCC] 2.4 - input_buf = '', - output_buf_len = 4096)) - m.tid = create_message.tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, infoCB, errback, - fid = create_message.payload.fid, file_attributes = create_message.payload.file_attributes) - messages_history.append(m) - else: - errback(OperationFailure('Failed to list %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(info_message.tid, kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes']) - else: - errback(OperationFailure('Failed to list %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 - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, - fid = fid, offset = offset, - remaining_len = remaining_len, - read_len = read_len, - file_attributes = file_attributes) - - def readCB(read_message, **kwargs): - # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history. - if read_message.status == 0: - 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(read_message.tid, kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes']) - else: - closeFid(read_message.tid, kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len )) - else: - messages_history.append(read_message) - closeFid(read_message.tid, kwargs['fid'], error = read_message.status) - - def closeFid(tid, fid, ret = None, error = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, ret = ret, error = error) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['ret'] is not None: - callback(kwargs['ret']) - elif kwargs['error'] is not None: - errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to retrieve %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _storeFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30): - self._storeFileFromOffset_SMB2(service_name, path, file_obj, callback, errback, 0L, True, timeout) - - def _storeFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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 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 -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - 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, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE, - create_disp = FILE_OVERWRITE_IF if truncate else FILE_OPEN_IF, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, 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(write_message.tid, kwargs['fid'], kwargs['offset']) - else: - messages_history.append(write_message) - closeFid(write_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history)) - - def closeFid(tid, fid, error = None, offset = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, fid = fid, offset = offset, error = error) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['offset'] is not None: - callback(( file_obj, kwargs['offset'] )) # Note that this is a tuple of 2-elements - elif kwargs['error'] is not None: - errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to store %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - - def _deleteFiles_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path_file_pattern.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = DELETE | FILE_READ_ATTRIBUTES, - 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, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(open_message, **kwargs): - messages_history.append(open_message) - if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) - 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 = '\x01')) - ''' - Resources: - https://msdn.microsoft.com/en-us/library/cc246560.aspx - https://msdn.microsoft.com/en-us/library/cc232098.aspx - ''' - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, fid = fid) - messages_history.append(m) - - def deleteCB(delete_message, **kwargs): - messages_history.append(delete_message) - if delete_message.status == 0: - closeFid(delete_message.tid, kwargs['fid'], status = 0) - else: - closeFid(delete_message.tid, kwargs['fid'], status = delete_message.status) - - def closeFid(tid, fid, status = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, status = status) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['status'] == 0: - callback(path_file_pattern) - else: - errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to delete %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path_file_pattern.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_WRITE_ATTRIBUTES, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = 0, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(open_message, **kwargs): - messages_history.append(open_message) - if open_message.status == 0: - sendReset(open_message.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): - m = SMB2Message(SMB2SetInfoRequest(fid, - additional_info = 0, - info_type = SMB2_INFO_FILE, - file_info_class = 4, # FileBasicInformation - data = struct.pack('qqqqii',0,0,0,0,0x80,0))) # FILE_ATTRIBUTE_NORMAL - ''' - Resources: - https://msdn.microsoft.com/en-us/library/cc246560.aspx - https://msdn.microsoft.com/en-us/library/cc232064.aspx - https://msdn.microsoft.com/en-us/library/cc232094.aspx - https://msdn.microsoft.com/en-us/library/cc232110.aspx - ''' - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, fid = fid) - messages_history.append(m) - - def resetCB(reset_message, **kwargs): - messages_history.append(reset_message) - if reset_message.status == 0: - closeFid(reset_message.tid, kwargs['fid'], status = 0) - else: - closeFid(reset_message.tid, kwargs['fid'], status = reset_message.status) - - def closeFid(tid, fid, status = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, status = status) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['status'] == 0: - callback(path_file_pattern) - else: - errback(OperationFailure('Failed to reset attributes of %s on %s: Reset failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to reset attributes of %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _createDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | READ_CONTROL | DELETE | SYNCHRONIZE, - share_access = 0, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, - create_disp = FILE_CREATE, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - closeFid(create_message.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 = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - callback(path) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to create directory %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _deleteDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = DELETE | FILE_READ_ATTRIBUTES, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_DIRECTORY_FILE, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(open_message, **kwargs): - messages_history.append(open_message) - if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) - else: - errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( 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 = '\x01')) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, fid = fid) - messages_history.append(m) - - def deleteCB(delete_message, **kwargs): - messages_history.append(delete_message) - if delete_message.status == 0: - closeFid(delete_message.tid, kwargs['fid'], status = 0) - else: - closeFid(delete_message.tid, kwargs['fid'], status = delete_message.status) - - def closeFid(tid, fid, status = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['status'] == 0: - callback(path) - else: - errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to delete %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _rename_SMB2(self, service_name, old_path, new_path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - messages_history = [ ] - - 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] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(old_path, - file_attributes = 0, - access_mask = DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_SYNCHRONOUS_IO_NONALERT, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - sendRename(create_message.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 = '\x00'*16 + struct.pack('<I', len(new_path)*2) + new_path.encode('UTF-16LE') - m = SMB2Message(SMB2SetInfoRequest(fid, - additional_info = 0, - info_type = SMB2_INFO_FILE, - file_info_class = 0x0a, # SMB2_FILE_RENAME_INFO - data = data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, fid = fid) - messages_history.append(m) - - def renameCB(rename_message, **kwargs): - messages_history.append(rename_message) - if rename_message.status == 0: - closeFid(rename_message.tid, kwargs['fid'], status = 0) - else: - closeFid(rename_message.tid, kwargs['fid'], status = rename_message.status) - - def closeFid(tid, fid, status = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['status'] == 0: - callback(( old_path, new_path )) - else: - errback(OperationFailure('Failed to rename %s on %s: Rename failed' % ( old_path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to rename %s on %s: Unable to connect to shared device' % ( old_path, service_name ), messages_history)) - - m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ))) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _listSnapshots_SMB2(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendCreate(tid): - create_context_data = binascii.unhexlify(""" -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 00 00 00 00 10 00 04 00 -00 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00 -""".replace(' ', '').replace('\n', '')) - m = SMB2Message(SMB2CreateRequest(path, - file_attributes = 0, - access_mask = FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, - share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, - oplock = SMB2_OPLOCK_LEVEL_NONE, - impersonation = SEC_IMPERSONATE, - create_options = FILE_SYNCHRONOUS_IO_NONALERT, - create_disp = FILE_OPEN, - create_context_data = create_context_data)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if create_message.status == 0: - sendEnumSnapshots(create_message.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 = '')) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, enumSnapshotsCB, errback, tid = tid, fid = fid) - messages_history.append(m) - - 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(*map(int, ( s[5:9], s[10:12], s[13:15], s[16:18], s[19:21], s[22:24] )))) - closeFid(kwargs['tid'], kwargs['fid'], results = results) - else: - closeFid(kwargs['tid'], kwargs['fid'], status = enum_message.status) - - def closeFid(tid, fid, status = None, results = None): - m = SMB2Message(SMB2CloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status, results = results) - messages_history.append(m) - - def closeCB(close_message, **kwargs): - if kwargs['results'] is not None: - callback(kwargs['results']) - else: - errback(OperationFailure('Failed to list snapshots %s on %s: List failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if connect_message.status == 0: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to list snapshots %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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _echo_SMB2(self, data, callback, errback, timeout = 30): - messages_history = [ ] - - 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): - if smb_message.mid == 0: - smb_message.mid = self._getNextMID_SMB1() - if not smb_message.uid: - smb_message.uid = self.uid - if self.is_signing_active: - 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 - raw_data = smb_message.encode() - - 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: - smb_message.raw_data = smb_message.encode() - self.sendNMBMessage(smb_message.raw_data) - - def _getNextMID_SMB1(self): - self.mid += 1 - 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 - return self.mid - - def _updateState_SMB1(self, message): - if message.isReply: - if message.command == SMB_COM_NEGOTIATE: - if not message.status.hasError: - self.has_negotiated = True - self.log.info('SMB dialect negotiation successful (ExtendedSecurity:%s)', message.hasExtendedSecurity) - self._updateServerInfo(message.payload) - self._handleNegotiateResponse(message) - else: - raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_NEGOTIATE' % message.status.internal_value, - message.raw_data, message) - 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, 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 ), ex: - raise ProtocolError(str(ex), message.raw_data, message) - elif message.status.internal_value == 0xc000006d: # STATUS_LOGON_FAILURE - self.has_authenticated = False - self.log.info('Authentication (with extended security) failed. Please check username and password. You may need to enable/disable NTLMv2 authentication.') - self.onAuthFailed() - 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.has_authenticated = False - self.log.info('Authentication (without extended security) failed. Please check username and password') - self.onAuthFailed() - elif message.command == SMB_COM_TREE_CONNECT_ANDX: - try: - req = self.pending_requests[message.mid] - except KeyError: - pass - else: - if not message.status.hasError: - 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.capabilities = payload.capabilities - self.security_mode = payload.security_mode - 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 - - server_challenge, server_flags, server_info = ntlm.decodeChallengeMessage(ntlm_token) - if self.use_ntlm_v2: - self.log.info('Performing NTLMv2 authentication (with extended security) 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 (with extended security) 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 = ntlm.generateAuthenticateMessage(server_flags, - nt_challenge_response, - lm_challenge_response, - session_key, - self.username, - self.domain) - - 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) - self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__WithSecurityExtension(0, blob))) - - if self.security_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRE: - 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 & NEGOTIATE_SECURITY_SIGNATURES_ENABLE: - self.log.info('Server supports SMB signing') - self.is_signing_active = (self.sign_options == SMB.SIGN_WHEN_SUPPORTED) - else: - self.is_signing_active = False - - if self.is_signing_active: - self.log.info("SMB signing activated. All SMB messages will be signed.") - self.signing_session_key = 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 _handleNegotiateResponse_SMB1(self, message): - if message.uid and not self.uid: - self.uid = message.uid - - 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): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = 'IPC$' - messages_history = [ ] - - def connectSrvSvc(tid): - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback) - messages_history.append(m) - - def connectSrvSvcCB(create_message, **kwargs): - messages_history.append(create_message) - if not create_message.status.hasError: - call_id = self._getNextRPCCallID() - # 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) - # 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("""05 00 0b 03 10 00 00 00 48 00 00 00""".replace(' ', '')) + \ - struct.pack('<I', call_id) + \ - binascii.unhexlify(""" -b8 10 b8 10 00 00 00 00 01 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""".replace(' ', '').replace('\n', '')) - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, 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 not trans_message.status.hasError: - call_id = self._getNextRPCCallID() - - padding = '' - server_len = len(self.remote_name) + 1 - server_bytes_len = server_len * 2 - if server_len % 2 != 0: - padding = '\0\0' - server_bytes_len += 2 - - # See [MS-CIFS]: 2.2.5.6.1 for more information on TRANS_TRANSACT_NMPIPE (0x0026) parameters - setup_bytes = struct.pack('<HH', 0x0026, kwargs['fid']) - # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC. - # 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("""05 00 00 03 10 00 00 00""".replace(' ', '')) + \ - struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \ - binascii.unhexlify("""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(' ', '')) + \ - struct.pack('<III', server_len, 0, server_len) + \ - (self.remote_name + '\0').encode('UTF-16LE') + padding + \ - binascii.unhexlify(""" -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 -""".replace(' ', '').replace('\n', '')) - 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 = trans_message.tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) - messages_history.append(m) - else: - closeFid(trans_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to list shares: Unable to bind to Server Service RPC endpoint', messages_history)) - - def listShareResultsCB(result_message, **kwargs): - messages_history.append(result_message) - if not result_message.status.hasError: - # 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.data_bytes - - if ord(data_bytes[3]) & 0x02 == 0: - sendReadRequest(result_message.tid, kwargs['fid'], data_bytes) - else: - decodeResults(result_message.tid, kwargs['fid'], data_bytes) - else: - closeFid(result_message.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 - - for i in range(0, shares_count): - max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12]) - offset += 12 - results[i].name = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE') - - if length % 2 != 0: - offset += (length * 2 + 2) - else: - offset += (length * 2) - - max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12]) - offset += 12 - results[i].comments = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE') - - if length % 2 != 0: - offset += (length * 2 + 2) - else: - offset += (length * 2) - - closeFid(tid, fid) - callback(results) - - def sendReadRequest(tid, fid, data_bytes): - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, data_bytes = data_bytes) - - def readCB(read_message, **kwargs): - messages_history.append(read_message) - if not read_message.status.hasError: - data_len = read_message.payload.data_length - data_bytes = read_message.payload.data - - if ord(data_bytes[3]) & 0x02 == 0: - sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24]) - else: - decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24]) - else: - closeFid(read_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) - - def closeFid(tid, fid): - m = SMBMessage(ComCloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - messages_history.append(m) - - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path) - messages_history.append(m) - - def _listPath_SMB1(self, service_name, path, callback, errback, search, pattern, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if not path.endswith('\\'): - path += '\\' - messages_history = [ ] - results = [ ] - - 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, # SearchAttributes - 100, # SearchCount - 0x0006, # Flags: SMB_FIND_CLOSE_AT_EOS | SMB_FIND_RETURN_RESUME_KEYS - 0x0104, # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO - 0x0000) # SearchStorageType - if support_dfs: - params_bytes += ("\\" + self.remote_name + "\\" + service_name + path + pattern + '\0').encode('UTF-16LE') - else: - params_bytes += (path + pattern).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 - if support_dfs: - m.flags2 |= SMB_FLAGS2_DFS - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findFirstCB, errback, support_dfs=support_dfs) - messages_history.append(m) - - 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' - 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 = struct.unpack(info_format, data_bytes[offset:offset+info_size]) - - offset2 = offset + info_size - if offset2 + filename_length > data_length: - return data_bytes[offset:] - - filename = data_bytes[offset2:offset2+filename_length].decode('UTF-16LE') - short_name = short_name.decode('UTF-16LE') - 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)) - - if next_offset: - offset += next_offset - else: - break - return '' - - def findFirstCB(find_message, **kwargs): - messages_history.append(find_message) - if not find_message.status.hasError: - if not kwargs.has_key('total_count'): - # 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': '' }) - 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 not kwargs.has_key('data_count'): - 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']: - send_next = False - - 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, last_name_offset, 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 sendFindNext(tid, sid, resume_key, support_dfs=False): - setup_bytes = struct.pack('<H', 0x0002) # TRANS2_FIND_NEXT2 sub-command. See [MS-CIFS]: 2.2.6.3.1 - params_bytes = \ - struct.pack('<HHHIH', - sid, # SID - 100, # SearchCount - 0x0104, # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO - resume_key, # ResumeKey - 0x000a) # Flags: SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS | SMB_FIND_RETURN_RESUME_KEYS - if support_dfs: - params_bytes += ("\\" + self.remote_name + "\\" + service_name + path + pattern + '\0').encode('UTF-16LE') - else: - params_bytes += (path + pattern).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 - if support_dfs: - m.flags2 |= SMB_FLAGS2_DFS - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findNextCB, errback, sid = sid, support_dfs = support_dfs) - messages_history.append(m) - - def findNextCB(find_message, **kwargs): - messages_history.append(find_message) - if not find_message.status.hasError: - if not kwargs.has_key('total_count'): - # 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': '' }) - else: - end_of_search, last_name_offset = 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 not kwargs.has_key('data_count'): - 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']: - send_next = False - - if not send_next: - self.pending_requests[find_message.mid] = _PendingRequest(find_message.mid, expiry_time, findNextCB, errback, **kwargs) - elif end_of_search: - callback(results) - else: - sendFindNext(find_message.tid, kwargs['sid'], last_name_offset, 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 = 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 - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, dfsReferralCB, errback) - messages_history.append(m) - - def dfsReferralCB(dfs_message, **kwargs): - sendFindFirst(dfs_message.tid, True) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - if connect_message.payload.optional_support & SMB_TREE_CONNECTX_SUPPORT_DFS: - sendDFSReferral(connect_message.tid) - else: - sendFindFirst(connect_message.tid, False) - else: - errback(OperationFailure('Failed to list %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendFindFirst(self.connected_trees[service_name]) - - def _getAttributes_SMB1(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if path.startswith('\\'): - path = path[1:] - if path.endswith('\\'): - path = path[:-1] - messages_history = [ ] - - def sendQuery(tid): - setup_bytes = struct.pack('<H', 0x0005) # TRANS2_QUERY_PATH_INFORMATION sub-command. See [MS-CIFS]: 2.2.6.6.1 - params_bytes = \ - 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, - max_setup_count = 0, - params_bytes = params_bytes, - setup_bytes = setup_bytes)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback) - messages_history.append(m) - - def queryCB(query_message, **kwargs): - messages_history.append(query_message) - if not query_message.status.hasError: - info_format = '<QQQQIIQQ' - info_size = struct.calcsize(info_format) - create_time, last_access_time, last_write_time, last_attr_change_time, \ - file_attributes, _, alloc_size, file_size = struct.unpack(info_format, query_message.payload.data_bytes[:info_size]) - - info = SharedFile(create_time, last_access_time, last_write_time, last_attr_change_time, - file_size, alloc_size, file_attributes, unicode(path), unicode(path)) - callback(info) - else: - errback(OperationFailure('Failed to get attributes for %s on %s: Read failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendQuery(connect_message.tid) - else: - errback(OperationFailure('Failed to get attributes for %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendQuery(self.connected_trees[service_name]) - - def _retrieveFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30): - return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0L, -1L, timeout) - - def _retrieveFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path.replace('/', '\\') - messages_history = [ ] - - 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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback) - messages_history.append(m) - - def openCB(open_message, **kwargs): - messages_history.append(open_message) - 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, 0L )) - else: - sendRead(open_message.tid, open_message.payload.fid, starting_offset, open_message.payload.file_attributes, 0L, max_length) - else: - errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history)) - - def sendRead(tid, fid, offset, file_attributes, read_len, remaining_len): - read_count = self.max_raw_size - 2 - m = SMBMessage(ComReadAndxRequest(fid = fid, - offset = offset, - max_return_bytes_count = read_count, - min_return_bytes_count = min(0xFFFF, read_count))) - m.tid = tid - self._sendSMBMessage(m) - 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) - - def readCB(read_message, **kwargs): - # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history. - if not read_message.status.hasError: - read_len = kwargs['read_len'] - remaining_len = kwargs['remaining_len'] - data_len = read_message.payload.data_length - 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: - messages_history.append(read_message) - closeFid(read_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history)) - - def closeFid(tid, fid): - m = SMBMessage(ComCloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - messages_history.append(m) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendOpen(connect_message.tid) - else: - errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - 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, 0L, True, timeout) - - def _storeFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path.replace('/', '\\') - messages_history = [ ] - - def sendOpen(tid): - m = SMBMessage(ComOpenAndxRequest(filename = path, - 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. - search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM, - timeout = timeout * 1000)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback) - messages_history.append(m) - - def openCB(open_message, **kwargs): - messages_history.append(open_message) - if not open_message.status.hasError: - sendWrite(open_message.tid, open_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): - # For message signing, the total SMB message size must be not exceed the max_buffer_size. Non-message signing does not have this limitation - write_count = min((self.is_signing_active and (self.max_buffer_size-64)) or self.max_raw_size, 0xFFFF-1) # Need to minus 1 byte from 0xFFFF because of the first NULL byte in the ComWriteAndxRequest message data - data_bytes = file_obj.read(write_count) - data_len = len(data_bytes) - if data_len > 0: - m = SMBMessage(ComWriteAndxRequest(fid = fid, offset = offset, data_bytes = data_bytes)) - m.tid = tid - self._sendSMBMessage(m) - 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 - - 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 not write_message.status.hasError: - sendWrite(write_message.tid, kwargs['fid'], kwargs['offset']) - else: - messages_history.append(write_message) - closeFid(write_message.tid, kwargs['fid']) - errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history)) - - def closeFid(tid, fid): - m = SMBMessage(ComCloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - messages_history.append(m) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendOpen(connect_message.tid) - else: - errback(OperationFailure('Failed to store %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendOpen(self.connected_trees[service_name]) - - def _deleteFiles_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path_file_pattern.replace('/', '\\') - messages_history = [ ] - - def sendDelete(tid): - m = SMBMessage(ComDeleteRequest(filename_pattern = path, - search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback) - messages_history.append(m) - - def deleteCB(delete_message, **kwargs): - messages_history.append(delete_message) - if not delete_message.status.hasError: - callback(path_file_pattern) - else: - errback(OperationFailure('Failed to store %s on %s: Delete failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendDelete(connect_message.tid) - else: - errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendDelete(self.connected_trees[service_name]) - - def _resetFileAttributes_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): - raise NotReadyError('resetFileAttributes is not yet implemented for SMB1') - - def _createDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path.replace('/', '\\') - messages_history = [ ] - - def sendCreate(tid): - m = SMBMessage(ComCreateDirectoryRequest(path)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback) - messages_history.append(m) - - def createCB(create_message, **kwargs): - messages_history.append(create_message) - if not create_message.status.hasError: - callback(path) - else: - errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendCreate(connect_message.tid) - else: - errback(OperationFailure('Failed to create directory %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendCreate(self.connected_trees[service_name]) - - def _deleteDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - path = path.replace('/', '\\') - messages_history = [ ] - - def sendDelete(tid): - m = SMBMessage(ComDeleteDirectoryRequest(path)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback) - messages_history.append(m) - - def deleteCB(delete_message, **kwargs): - messages_history.append(delete_message) - if not delete_message.status.hasError: - callback(path) - else: - errback(OperationFailure('Failed to delete directory %s on %s: Delete failed' % ( path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendDelete(connect_message.tid) - else: - errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendDelete(self.connected_trees[service_name]) - - def _rename_SMB1(self, service_name, old_path, new_path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - new_path = new_path.replace('/', '\\') - old_path = old_path.replace('/', '\\') - messages_history = [ ] - - def sendRename(tid): - m = SMBMessage(ComRenameRequest(old_path = old_path, - new_path = new_path, - search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback) - messages_history.append(m) - - def renameCB(rename_message, **kwargs): - messages_history.append(rename_message) - if not rename_message.status.hasError: - callback(( old_path, new_path )) # Note that this is a tuple of 2-elements - else: - errback(OperationFailure('Failed to rename %s on %s: Rename failed' % ( old_path, service_name ), messages_history)) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendRename(connect_message.tid) - else: - errback(OperationFailure('Failed to rename %s on %s: Unable to connect to shared device' % ( old_path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendRename(self.connected_trees[service_name]) - - def _listSnapshots_SMB1(self, service_name, path, callback, errback, timeout = 30): - if not self.has_authenticated: - raise NotReadyError('SMB connection not authenticated') - - expiry_time = time.time() + timeout - path = path.replace('/', '\\') - if not path.endswith('\\'): - path += '\\' - messages_history = [ ] - results = [ ] - - 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 = 0, - timeout = timeout * 1000)) - m.tid = tid - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback) - messages_history.append(m) - - def openCB(open_message, **kwargs): - messages_history.append(open_message) - if not open_message.status.hasError: - 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)) - - def sendEnumSnapshots(tid, fid): - # [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._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, enumSnapshotsCB, errback, tid = tid, fid = fid) - messages_history.append(m) - - def enumSnapshotsCB(enum_message, **kwargs): - messages_history.append(enum_message) - if not enum_message.status.hasError: - results = [ ] - snapshots_count = struct.unpack('<I', enum_message.payload.data_bytes[4:8])[0] - for i in range(0, snapshots_count): - s = enum_message.payload.data_bytes[12+i*50:12+48+i*50].decode('UTF-16LE') - results.append(datetime(*map(int, ( s[5:9], s[10:12], s[13:15], s[16:18], s[19:21], s[22:24] )))) - closeFid(kwargs['tid'], kwargs['fid']) - callback(results) - else: - closeFid(kwargs['tid'], kwargs['fid']) - errback(OperationFailure('Failed to list snapshots %s on %s: Unable to list snapshots on path' % ( path, service_name ), messages_history)) - - def closeFid(tid, fid): - m = SMBMessage(ComCloseRequest(fid)) - m.tid = tid - self._sendSMBMessage(m) - messages_history.append(m) - - if not self.connected_trees.has_key(service_name): - def connectCB(connect_message, **kwargs): - messages_history.append(connect_message) - if not connect_message.status.hasError: - self.connected_trees[service_name] = connect_message.tid - sendOpen(connect_message.tid) - else: - errback(OperationFailure('Failed to list snapshots %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) - - m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name) - messages_history.append(m) - else: - sendOpen(self.connected_trees[service_name]) - - def _echo_SMB1(self, data, callback, errback, timeout = 30): - messages_history = [ ] - - def echoCB(echo_message, **kwargs): - messages_history.append(echo_message) - if not echo_message.status.hasError: - callback(echo_message.payload.data) - else: - errback(OperationFailure('Echo failed', messages_history)) - - m = SMBMessage(ComEchoRequest(echo_data = data)) - self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback) - messages_history.append(m) - - -
    [docs]class SharedDevice: - """ - Contains information about a single shared device on the remote server. - """ - - # 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 u'Shared device: %s (type:0x%02x comments:%s)' % (self.name, self.type, self.comments )
    - - -
    [docs]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). - """ - - def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename): - 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 - 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. - - @property - def isDirectory(self): - """A convenient 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) - - def __unicode__(self): - return u'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 -
    - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/docs/html/_modules/smb/security_descriptors.html b/docs/html/_modules/smb/security_descriptors.html new file mode 100644 index 0000000..cbd41f3 --- /dev/null +++ b/docs/html/_modules/smb/security_descriptors.html @@ -0,0 +1,459 @@ + + + + + + + + smb.security_descriptors — pysmb 1.1.25 documentation + + + + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for smb.security_descriptors

    +"""
    +This module implements security descriptors, and the partial structures
    +used in them, as specified in [MS-DTYP].
    +"""
    +
    +import struct
    +
    +
    +# Security descriptor control flags
    +# [MS-DTYP]: 2.4.6
    +SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
    +SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
    +SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
    +SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
    +SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
    +SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
    +SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
    +SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
    +SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
    +SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
    +SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
    +SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
    +SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
    +SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
    +SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
    +SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
    +
    +# ACE types
    +# [MS-DTYP]: 2.4.4.1
    +ACE_TYPE_ACCESS_ALLOWED = 0x00
    +ACE_TYPE_ACCESS_DENIED = 0x01
    +ACE_TYPE_SYSTEM_AUDIT = 0x02
    +ACE_TYPE_SYSTEM_ALARM = 0x03
    +ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
    +ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
    +ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
    +ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
    +ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
    +ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
    +ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
    +ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
    +ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
    +ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
    +ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
    +ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
    +ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
    +ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
    +ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
    +ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
    +
    +# ACE flags
    +# [MS-DTYP]: 2.4.4.1
    +ACE_FLAG_OBJECT_INHERIT = 0x01
    +ACE_FLAG_CONTAINER_INHERIT = 0x02
    +ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
    +ACE_FLAG_INHERIT_ONLY = 0x08
    +ACE_FLAG_INHERITED = 0x10
    +ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
    +ACE_FLAG_FAILED_ACCESS = 0x80
    +
    +# Pre-defined well-known SIDs
    +# [MS-DTYP]: 2.4.2.4
    +SID_NULL = "S-1-0-0"
    +SID_EVERYONE = "S-1-1-0"
    +SID_LOCAL = "S-1-2-0"
    +SID_CONSOLE_LOGON = "S-1-2-1"
    +SID_CREATOR_OWNER = "S-1-3-0"
    +SID_CREATOR_GROUP = "S-1-3-1"
    +SID_OWNER_SERVER = "S-1-3-2"
    +SID_GROUP_SERVER = "S-1-3-3"
    +SID_OWNER_RIGHTS = "S-1-3-4"
    +SID_NT_AUTHORITY = "S-1-5"
    +SID_DIALUP = "S-1-5-1"
    +SID_NETWORK = "S-1-5-2"
    +SID_BATCH = "S-1-5-3"
    +SID_INTERACTIVE = "S-1-5-4"
    +SID_SERVICE = "S-1-5-6"
    +SID_ANONYMOUS = "S-1-5-7"
    +SID_PROXY = "S-1-5-8"
    +SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
    +SID_PRINCIPAL_SELF = "S-1-5-10"
    +SID_AUTHENTICATED_USERS = "S-1-5-11"
    +SID_RESTRICTED_CODE = "S-1-5-12"
    +SID_TERMINAL_SERVER_USER = "S-1-5-13"
    +SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
    +SID_THIS_ORGANIZATION = "S-1-5-15"
    +SID_IUSR = "S-1-5-17"
    +SID_LOCAL_SYSTEM = "S-1-5-18"
    +SID_LOCAL_SERVICE = "S-1-5-19"
    +SID_NETWORK_SERVICE = "S-1-5-20"
    +SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
    +SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
    +SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
    +SID_BUILTIN_USERS = "S-1-5-32-545"
    +SID_BUILTIN_GUESTS = "S-1-5-32-546"
    +SID_POWER_USERS = "S-1-5-32-547"
    +SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
    +SID_SERVER_OPERATORS = "S-1-5-32-549"
    +SID_PRINTER_OPERATORS = "S-1-5-32-550"
    +SID_BACKUP_OPERATORS = "S-1-5-32-551"
    +SID_REPLICATOR = "S-1-5-32-552"
    +SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
    +SID_REMOTE_DESKTOP = "S-1-5-32-555"
    +SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
    +SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
    +SID_PERFMON_USERS = "S-1-5-32-558"
    +SID_PERFLOG_USERS = "S-1-5-32-559"
    +SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
    +SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
    +SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
    +SID_IIS_IUSRS = "S-1-5-32-568"
    +SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
    +SID_EVENT_LOG_READERS = "S-1-5-32-573"
    +SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
    +SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
    +SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
    +SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
    +SID_HYPER_V_ADMINS = "S-1-5-32-578"
    +SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
    +SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
    +SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
    +SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
    +SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
    +SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
    +SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
    +SID_NT_SERVICE = "S-1-5-80"
    +SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
    +SID_LOCAL_ACCOUNT = "S-1-5-113"
    +SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
    +SID_OTHER_ORGANIZATION = "S-1-5-1000"
    +SID_ALL_APP_PACKAGES = "S-1-15-2-1"
    +SID_ML_UNTRUSTED = "S-1-16-0"
    +SID_ML_LOW = "S-1-16-4096"
    +SID_ML_MEDIUM = "S-1-16-8192"
    +SID_ML_MEDIUM_PLUS = "S-1-16-8448"
    +SID_ML_HIGH = "S-1-16-12288"
    +SID_ML_SYSTEM = "S-1-16-16384"
    +SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
    +SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
    +SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
    +SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
    +SID_KEY_TRUST_IDENTITY = "S-1-18-4"
    +SID_KEY_PROPERTY_MFA = "S-1-18-5"
    +SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
    +
    +
    +
    [docs]class SID(object): + """ + A Windows security identifier. Represents a single principal, such a + user or a group, as a sequence of numbers consisting of the revision, + identifier authority, and a variable-length list of subauthorities. + + See [MS-DTYP]: 2.4.2 + """ + def __init__(self, revision, identifier_authority, subauthorities): + #: Revision, should always be 1. + self.revision = revision + #: An integer representing the identifier authority. + self.identifier_authority = identifier_authority + #: A list of integers representing all subauthorities. + self.subauthorities = subauthorities + + def __str__(self): + """ + String representation, as specified in [MS-DTYP]: 2.4.2.1 + """ + if self.identifier_authority >= 2**32: + id_auth = '%#x' % (self.identifier_authority,) + else: + id_auth = self.identifier_authority + auths = [self.revision, id_auth] + self.subauthorities + return 'S-' + '-'.join(str(subauth) for subauth in auths) + + def __repr__(self): + return 'SID(%r)' % (str(self),) + + @classmethod + def from_bytes(cls, data, return_tail=False): + revision, subauth_count = struct.unpack('<BB', data[:2]) + identifier_authority = struct.unpack('>Q', '\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
    + + +
    [docs]class ACE(object): + """ + Represents a single access control entry. + + See [MS-DTYP]: 2.4.4 + """ + HEADER_FORMAT = '<BBH' + + def __init__(self, type_, flags, mask, sid, additional_data): + #: An integer representing the type of the ACE. One of the + #: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field + #: from [MS-DTYP] 2.4.4.1. + self.type = type_ + #: An integer bitmask with ACE flags, corresponds to the + #: ``AceFlags`` field. + self.flags = flags + #: An integer representing the ``ACCESS_MASK`` as specified in + #: [MS-DTYP] 2.4.3. + self.mask = mask + #: The :class:`SID` of a trustee. + self.sid = sid + #: A dictionary of additional fields present in the ACE, depending + #: on the type. The following fields can be present: + #: + #: * ``flags`` + #: * ``object_type`` + #: * ``inherited_object_type`` + #: * ``application_data`` + #: * ``attribute_data`` + self.additional_data = additional_data + + def __repr__(self): + return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % ( + self.type, self.flags, self.mask, self.sid, + ) + + @property + def isInheritOnly(self): + """Convenience property which indicates if this ACE is inherit + only, meaning that it doesn't apply to the object itself.""" + return bool(self.flags & ACE_FLAG_INHERIT_ONLY) + + @classmethod + def from_bytes(cls, data): + header_size = struct.calcsize(cls.HEADER_FORMAT) + header = data[:header_size] + type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header) + + assert len(data) >= size + + body = data[header_size:size] + additional_data = {} + + # In all ACE types, the mask immediately follows the header. + mask = struct.unpack('<I', body[:4])[0] + body = body[4:] + + # All OBJECT-type ACEs contain additional flags, and two GUIDs as + # the following fields. + if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT, + ACE_TYPE_ACCESS_DENIED_OBJECT, + ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT, + ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT, + ACE_TYPE_SYSTEM_AUDIT_OBJECT, + ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT): + additional_data['flags'] = struct.unpack('<I', body[:4])[0] + additional_data['object_type'] = body[4:20] + additional_data['inherited_object_type'] = body[20:36] + body = body[36:] + + # Then the SID in all types. + sid, body = SID.from_bytes(body, return_tail=True) + + # CALLBACK-type ACEs (and for some obscure reason, + # SYSTEM_AUDIT_OBJECT) have a final tail of application data. + if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK, + ACE_TYPE_ACCESS_DENIED_CALLBACK, + ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT, + ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT, + ACE_TYPE_SYSTEM_AUDIT_OBJECT, + ACE_TYPE_SYSTEM_AUDIT_CALLBACK, + ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT): + additional_data['application_data'] = body + + # SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data. + if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE: + additional_data['attribute_data'] = body + + return cls(type_, flags, mask, sid, additional_data)
    + + +
    [docs]class ACL(object): + """ + Access control list, encapsulating a sequence of access control + entries. + + See [MS-DTYP]: 2.4.5 + """ + HEADER_FORMAT = '<BBHHH' + + def __init__(self, revision, aces): + #: Integer value of the revision. + self.revision = revision + #: List of :class:`ACE` instances. + self.aces = aces + + def __repr__(self): + return "ACL(%r)" % (self.aces,) + + @classmethod + def from_bytes(cls, data): + revision = None + aces = [] + + header_size = struct.calcsize(cls.HEADER_FORMAT) + header, remaining = data[:header_size], data[header_size:] + revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header) + + assert len(data) >= size + + for i in range(count): + ace_size = struct.unpack('<H', remaining[2:4])[0] + ace_data, remaining = remaining[:ace_size], remaining[ace_size:] + aces.append(ACE.from_bytes(ace_data)) + + return cls(revision, aces)
    + + +
    [docs]class SecurityDescriptor(object): + """ + Represents a security descriptor. + + See [MS-DTYP]: 2.4.6 + """ + + HEADER_FORMAT = '<BBHIIII' + + def __init__(self, flags, owner, group, dacl, sacl): + #: Integer bitmask of control flags. Corresponds to the + #: ``Control`` field in [MS-DTYP] 2.4.6. + self.flags = flags + #: Instance of :class:`SID` representing the owner user. + self.owner = owner + #: Instance of :class:`SID` representing the owner group. + self.group = group + #: Instance of :class:`ACL` representing the discretionary access + #: control list, which specifies access restrictions of an object. + self.dacl = dacl + #: Instance of :class:`ACL` representing the system access control + #: list, which specifies audit logging of an object. + self.sacl = sacl + + @classmethod + def from_bytes(cls, data): + owner = None + group = None + dacl = None + sacl = None + + header = data[:struct.calcsize(cls.HEADER_FORMAT)] + (revision, sbz1, flags, owner_offset, group_offset, sacl_offset, + dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header) + + assert revision == 1 + assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE + for offset in (owner_offset, group_offset, sacl_offset, dacl_offset): + assert 0 <= offset < len(data) + + if owner_offset: + owner = SID.from_bytes(data[owner_offset:]) + if group_offset: + group = SID.from_bytes(data[group_offset:]) + if dacl_offset: + dacl = ACL.from_bytes(data[dacl_offset:]) + if sacl_offset: + sacl = ACL.from_bytes(data[sacl_offset:]) + + return cls(flags, owner, group, dacl, sacl)
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/html/_modules/smb/smb_structs.html b/docs/html/_modules/smb/smb_structs.html index 61587b1..e90a726 100644 --- a/docs/html/_modules/smb/smb_structs.html +++ b/docs/html/_modules/smb/smb_structs.html @@ -6,7 +6,7 @@ - smb.smb_structs — pysmb 1.1.18 documentation + smb.smb_structs — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -73,17 +76,20 @@ # Set to True if you want to enable SMB2 protocol. SUPPORT_SMB2 = True +# Set to True if you want to enable SMB2.1 and above protocol. +SUPPORT_SMB2x = True + # Supported dialects -DIALECTS = [ ] -for i, ( name, dialect ) in enumerate([ ( 'NT_LAN_MANAGER_DIALECT', 'NT LM 0.12' ), ]): - DIALECTS.append(dialect) - globals()[name] = i - -DIALECTS2 = [ ] -for i, ( name, dialect ) in enumerate([ ( 'SMB2_DIALECT', 'SMB 2.002' ) ]): - DIALECTS2.append(dialect) - globals()[name] = i + len(DIALECTS) - +NT_LAN_MANAGER_DIALECT = 0 # 'NT LM 0.12' is always the first element in the dialect list and must always be included (MS-SMB 2.2.4.5.1) + +# Return the list of support SMB dialects based on the SUPPORT_x constants +def init_dialects_list(): + dialects = [ 'NT LM 0.12' ] + if SUPPORT_SMB2: + dialects.append('SMB 2.002') + if SUPPORT_SMB2x: + dialects.append('SMB 2.???') + return dialects
    [docs]class UnsupportedFeature(Exception): """ @@ -171,8 +177,9 @@ log = logging.getLogger('SMB.SMBMessage') protocol = 1 - def __init__(self, payload = None): + def __init__(self, conn, payload = None): self.reset() + self.conn = conn if payload: self.payload = payload self.payload.initMessage(self) @@ -353,10 +360,7 @@ def prepare(self, message): assert message.payload == self message.parameters_data = '' - if SUPPORT_SMB2: - message.data = ''.join(map(lambda s: '\x02'+s+'\x00', DIALECTS + DIALECTS2)) - else: - message.data = ''.join(map(lambda s: '\x02'+s+'\x00', DIALECTS)) + message.data = ''.join(map(lambda s: '\x02'+s+'\x00', init_dialects_list())) class ComNegotiateResponse(Payload): @@ -1343,7 +1347,7 @@ - [MS-CIFS]: 2.2.4.39.1 """ - def __init__(self, echo_data = '', echo_count = 1): + def __init__(self, echo_data = b'', echo_count = 1): self.echo_count = echo_count self.echo_data = echo_data @@ -1494,12 +1498,15 @@
  • index
  • - +
  • + modules |
  • +
    diff --git a/docs/html/_sources/api/smb_security_descriptors.txt b/docs/html/_sources/api/smb_security_descriptors.txt new file mode 100644 index 0000000..0f048fe --- /dev/null +++ b/docs/html/_sources/api/smb_security_descriptors.txt @@ -0,0 +1,23 @@ + +Security Descriptors +==================== + +.. module:: smb.security_descriptors + :synopsis: Data structures used in Windows security descriptors. + +This module implements security descriptors, and associated data +structures, as specified in `[MS-DTYP]`_. + +.. autoclass:: SID + :members: + +.. autoclass:: ACE + :members: + +.. autoclass:: ACL + :members: + +.. autoclass:: SecurityDescriptor + :members: + +.. _[MS-DTYP]: https://msdn.microsoft.com/en-us/library/cc230273.aspx diff --git a/docs/html/api/nmb_NBNSProtocol.html b/docs/html/api/nmb_NBNSProtocol.html index 263abd9..fdbaa08 100644 --- a/docs/html/api/nmb_NBNSProtocol.html +++ b/docs/html/api/nmb_NBNSProtocol.html @@ -6,7 +6,7 @@ - NBNSProtocol Class — pysmb 1.1.18 documentation + NBNSProtocol Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/nmb_NetBIOS.html b/docs/html/api/nmb_NetBIOS.html index 9bf23b5..165ff63 100644 --- a/docs/html/api/nmb_NetBIOS.html +++ b/docs/html/api/nmb_NetBIOS.html @@ -6,7 +6,7 @@ - NetBIOS class — pysmb 1.1.18 documentation + NetBIOS class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_SMBConnection.html b/docs/html/api/smb_SMBConnection.html index 3caf88c..38e03ac 100644 --- a/docs/html/api/smb_SMBConnection.html +++ b/docs/html/api/smb_SMBConnection.html @@ -6,7 +6,7 @@ - SMBConnection Class — pysmb 1.1.18 documentation + SMBConnection Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + @@ -547,16 +155,19 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_SMBHandler.html b/docs/html/api/smb_SMBHandler.html index e4aaf64..ff3e9ac 100644 --- a/docs/html/api/smb_SMBHandler.html +++ b/docs/html/api/smb_SMBHandler.html @@ -6,7 +6,7 @@ - SMbHandler Class — pysmb 1.1.18 documentation + SMbHandler Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_SMBProtocolFactory.html b/docs/html/api/smb_SMBProtocolFactory.html index def8ac3..232c75f 100644 --- a/docs/html/api/smb_SMBProtocolFactory.html +++ b/docs/html/api/smb_SMBProtocolFactory.html @@ -6,7 +6,7 @@ - SMBProtocolFactory Class — pysmb 1.1.18 documentation + SMBProtocolFactory Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + @@ -586,16 +192,19 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_SharedDevice.html b/docs/html/api/smb_SharedDevice.html index c13b6d0..46d17a9 100644 --- a/docs/html/api/smb_SharedDevice.html +++ b/docs/html/api/smb_SharedDevice.html @@ -6,7 +6,7 @@ - SharedDevice Class — pysmb 1.1.18 documentation + SharedDevice Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_SharedFile.html b/docs/html/api/smb_SharedFile.html index 7b4e37c..7a708e2 100644 --- a/docs/html/api/smb_SharedFile.html +++ b/docs/html/api/smb_SharedFile.html @@ -6,7 +6,7 @@ - SharedFile Class — pysmb 1.1.18 documentation + SharedFile Class — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/api/smb_exceptions.html b/docs/html/api/smb_exceptions.html index aac3042..ebc21c7 100644 --- a/docs/html/api/smb_exceptions.html +++ b/docs/html/api/smb_exceptions.html @@ -6,7 +6,7 @@ - SMB Exceptions — pysmb 1.1.18 documentation + SMB Exceptions — pysmb 1.1.25 documentation @@ -14,7 +14,7 @@ - - + + @@ -35,12 +35,15 @@ index
  • - modules |
  • +
  • + next |
  • previous |
  • - +