diff --git a/CHANGELOG b/CHANGELOG index e52126b..d59f598 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,102 @@ +pysmb-1.2.6, 9 Dec 2020 +======================= +- Fix bug in SMB1 store file implmentation which generates SMB_COM_WRITE_ANDX + packets larger than the allowed max buffer size (#175) + +pysmb-1.2.5, 18 Oct 2020 +======================= +- Fix bug in filename encoding which leads to failure for file retrieval and upload operations (#170 #171). +- Improve resetFileAttributes() method in SMBConnection class to allow the + new attribute to be specified in the reset operation (#172). + +pysmb-1.2.4, 6 Oct 2020 +======================= +- Remove dependency on pycrypto as it is no longer under active maintenance + +pysmb-1.2.3, 6 Oct 2020 +======================= +- Fix bug in session key generation during session negotiation (#166) +- Fix bug in SMB message signing which leads to operation failures with Samba services. + +pysmb-1.2.2, 5 Sep 2020 +======================= +- Improve SMB URL handlers to support specifying server's machine name and IP + address. (#162) +- Improvements to documentation on SMB URLs (#160) + +pysmb-1.2.1, 17 May 2020 +======================== +- Fix bug in deleteFiles() method which can fail for certain search patterns. + +pysmb-1.2.0, 17 May 2020 +========================= +- Add new parameter, delete_matching_folders, to deleteFiles() method to + support deletion of child folders that match the search pattern. + +pysmb-1.1.29, 16 May 2020 +========================= +- Fix unhandled exception for short NBNS queries #149 +- Fix wildcard file deletion with servers on SMB2 protocol #33 + +pysmb-1.1.28, 23 Nov 2019 +======================== +- SharedFile instances returned from the listPath() method now has a new + file_id attribute which represents the file reference number given by the SMB server. + +pysmb-1.1.27, 9 Jan 2019 +======================== +- Remove support for SMB-2.1 dialect as it seems to have issues with Windows 2008 R2 + +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..a02f43a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (C) 2001-2015 Michael Teo +Copyright (C) 2001-2020 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..eb471d0 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pysmb -Version: 1.1.19 +Version: 1.2.6 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 diff --git a/docs/doctrees/api/nmb_NBNSProtocol.doctree b/docs/doctrees/api/nmb_NBNSProtocol.doctree index 63a5cd0..b69c5c9 100644 Binary files a/docs/doctrees/api/nmb_NBNSProtocol.doctree and b/docs/doctrees/api/nmb_NBNSProtocol.doctree differ diff --git a/docs/doctrees/api/nmb_NetBIOS.doctree b/docs/doctrees/api/nmb_NetBIOS.doctree index 9b279b3..fc1e88e 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..593caf4 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..2481234 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..dccca6e 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..637a4fc 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..68050e3 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_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..cba0814 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..cf4fb9f 100644 Binary files a/docs/doctrees/index.doctree and b/docs/doctrees/index.doctree differ diff --git a/docs/doctrees/upgrading.doctree b/docs/doctrees/upgrading.doctree new file mode 100644 index 0000000..d6f3158 Binary files /dev/null and b/docs/doctrees/upgrading.doctree differ diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index bf80f8a..5069b78 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: 25ba8f2e92503ade60b28e1a69a6901b tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_modules/index.html b/docs/html/_modules/index.html index c518f1e..d2a25a5 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.2.1 documentation @@ -14,7 +14,7 @@ - + diff --git a/docs/html/_modules/nmb/NetBIOS.html b/docs/html/_modules/nmb/NetBIOS.html index 82bdf75..dba8c96 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.2.1 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..c614043 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.2.1 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 index 1df1ca8..2a3b042 100644 --- a/docs/html/_modules/smb/SMBConnection.html +++ b/docs/html/_modules/smb/SMBConnection.html @@ -6,7 +6,7 @@ - smb.SMBConnection — pysmb 1.1.18 documentation + smb.SMBConnection — pysmb 1.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -84,6 +87,7 @@ Create a new SMBConnection instance. *username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server. + *password* can be a string or a callable returning a string. File operations can only be proceeded after the connection has been authenticated successfully. Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication. @@ -135,6 +139,15 @@ total_sent = total_sent + sent # + # Support for "with" context + # + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + # # Misc Properties # @@ -216,15 +229,23 @@ 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, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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. """ @@ -308,6 +329,37 @@ 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 getSecurity(self, service_name, path, timeout = 30): + """ + Retrieve the security descriptor of the file at *path* on the *service_name*. + + :param string/unicode service_name: the name of the shared folder for the *path* + :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised. + :return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file. + """ + if not self.sock: + raise NotConnectedError('Not connected to server') + + results = [ ] + + def cb(info): + self.is_busy = False + results.append(info) + + def eb(failure): + self.is_busy = False + raise failure + + self.is_busy = True + try: + self._getSecurity(service_name, path, cb, eb, timeout) while self.is_busy: self._pollForNetBIOSPacket(timeout) finally: @@ -413,9 +465,11 @@ return results[0]
    -
    [docs] def deleteFiles(self, service_name, path_file_pattern, timeout = 30): +
    [docs] def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30): """ Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request. + + If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively. :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. @@ -435,7 +489,7 @@ self.is_busy = True try: - self._deleteFiles(service_name, path_file_pattern, cb, eb, timeout = timeout) + self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout) while self.is_busy: self._pollForNetBIOSPacket(timeout) finally: @@ -558,7 +612,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :return: The *data* parameter """ if not self.sock: @@ -656,12 +710,15 @@
  • index
  • - +
  • + modules |
  • +
    diff --git a/docs/html/_modules/smb/SMBProtocol.html b/docs/html/_modules/smb/SMBProtocol.html index 20cbc73..862a0b1 100644 --- a/docs/html/_modules/smb/SMBProtocol.html +++ b/docs/html/_modules/smb/SMBProtocol.html @@ -6,7 +6,7 @@ - smb.SMBProtocol — pysmb 1.1.18 documentation + smb.SMBProtocol — pysmb 1.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • +
    @@ -240,15 +243,23 @@ 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, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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. @@ -431,7 +442,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter. """ @@ -475,12 +486,15 @@
  • index
  • - +
  • + modules |
  • +
    diff --git a/docs/html/_modules/smb/base.html b/docs/html/_modules/smb/base.html index 9b94c8d..48809fc 100644 --- a/docs/html/_modules/smb/base.html +++ b/docs/html/_modules/smb/base.html @@ -6,7 +6,7 @@ - smb.base — pysmb 1.1.18 documentation + smb.base — pysmb 1.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -68,6 +71,7 @@ from smb2_constants import * from smb_structs import * from smb2_structs import * +from .security_descriptors import SecurityDescriptor from nmb.base import NMBSession from utils import convertFILETIMEtoEpoch import ntlm, securityblob @@ -122,13 +126,14 @@ 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._password = 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.async_requests = { } #: AsyncID mapped to _PendingRequest instance self.pending_requests = { } #: MID mapped to _PendingRequest instance self.connected_trees = { } #: Share name mapped to TID self.next_rpc_call_id = 1 #: Next RPC callID value. Not used directly in SMB message. Usually encapsulated in sub-commands under SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2 messages @@ -167,6 +172,10 @@ (self.use_ntlm_v2 and 'v2') or 'v1', (SUPPORT_EXTENDED_SECURITY and 'with') or 'without') + @property + def password(self): + password = self._password() if callable(self._password) else self._password + return _convert_to_unicode(password) # # NMBSession Methods @@ -236,6 +245,7 @@ self._listShares = self._listShares_SMB1 self._listPath = self._listPath_SMB1 self._listSnapshots = self._listSnapshots_SMB1 + self._getSecurity = self._getSecurity_SMB1 self._getAttributes = self._getAttributes_SMB1 self._retrieveFile = self._retrieveFile_SMB1 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1 @@ -259,6 +269,7 @@ self._listPath = self._listPath_SMB2 self._listSnapshots = self._listSnapshots_SMB2 self._getAttributes = self._getAttributes_SMB2 + self._getSecurity = self._getSecurity_SMB2 self._retrieveFile = self._retrieveFile_SMB2 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB2 self._storeFile = self._storeFile_SMB2 @@ -282,7 +293,7 @@ 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: + if smb_message.command != SMB2_COM_NEGOTIATE: smb_message.session_id = self.session_id if self.is_signing_active: @@ -319,6 +330,19 @@ if result == securityblob.RESULT_ACCEPT_COMPLETED: self.has_authenticated = True self.log.info('Authentication (on SMB2) successful!') + + # [MS-SMB2]: 3.2.5.3.1 + # If the security subsystem indicates that the session was established by an anonymous user, + # Session.SigningRequired MUST be set to FALSE. + # If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the + # SMB2 SESSION_SETUP Response and if Session.SigningRequired is TRUE, this indicates a SESSION_SETUP + # failure and the connection MUST be terminated. If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags + # field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired + # MUST be set to FALSE. + if message.payload.isGuestSession or message.payload.isAnonymousSession: + self.is_signing_active = False + self.log.info('Signing disabled because session is guest/anonymous') + self.onAuthOK() else: raise ProtocolError('SMB2_COM_SESSION_SETUP status is 0 but security blob negResult value is %d' % result, message.raw_data, message) @@ -332,18 +356,58 @@ 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 + elif (message.status == 0xc000006d # STATUS_LOGON_FAILURE + or message.status == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status == 0xc000006a):# STATUS_WRONG_PASSWORD self.has_authenticated = False self.log.info('Authentication (on SMB2) failed. Please check username and password.') self.onAuthFailed() + elif (message.status == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Not allowed.') + self.onAuthFailed() + elif message.status == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Workstation not trusted.') + 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 + if message.isAsync: + if message.status == 0x00000103: # STATUS_PENDING + req = self.pending_requests.pop(message.mid, None) + if req: + self.async_requests[message.async_id] = req + else: # All other status including SUCCESS + req = self.async_requests.pop(message.async_id, None) + if req: + req.callback(message, **req.kwargs) + return True + else: + req = self.pending_requests.pop(message.mid, None) + if req: + req.callback(message, **req.kwargs) + return True def _updateServerInfo_SMB2(self, payload): @@ -383,7 +447,8 @@ lm_challenge_response, session_key, self.username, - self.domain) + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -432,7 +497,7 @@ m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback, tid = tid) messages_history.append(m) def connectSrvSvcCB(create_message, **kwargs): @@ -454,9 +519,9 @@ 01 00 00 00 """.replace(' ', '').replace('\n', '')) m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0)) - m.tid = create_message.tid + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) messages_history.append(m) else: errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history)) @@ -465,12 +530,12 @@ 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 + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint') def rpcReadCB(read_message, **kwargs): messages_history.append(read_message) @@ -498,12 +563,12 @@ 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 + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint') def listShareResultsCB(result_message, **kwargs): messages_history.append(result_message) @@ -512,13 +577,11 @@ 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], data_bytes) + else: + decodeResults(kwargs['tid'], kwargs['fid'], data_bytes) + else: + closeFid(kwargs['tid'], kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) def decodeResults(tid, fid, data_bytes): @@ -557,20 +620,19 @@ 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) + tid = tid, 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + closeFid(kwargs['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): @@ -635,39 +697,44 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: - sendQuery(create_message.tid, create_message.payload.fid, '') + sendQuery(kwargs['tid'], create_message.payload.fid, '') + elif create_message.status == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to list %s on %s: Path not found' % ( path, service_name ), messages_history)) else: errback(OperationFailure('Failed to list %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) def sendQuery(tid, fid, data_buf): m = SMB2Message(SMB2QueryDirectoryRequest(fid, pattern, - info_class = 0x03, # FileBothDirectoryInformation + info_class = 0x25, # FileIdBothDirectoryInformation 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = tid, 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) + sendQuery(kwargs['tid'], kwargs['fid'], data_buf) + elif query_message.status == 0xC000000FL: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing + closeFid(kwargs['tid'], kwargs['fid'], results = results) elif query_message.status == 0x80000006L: # STATUS_NO_MORE_FILES - closeFid(query_message.tid, kwargs['fid'], results = results) - else: - closeFid(query_message.tid, kwargs['fid'], error = query_message.status) + closeFid(kwargs['tid'], kwargs['fid'], results = results) + else: + closeFid(kwargs['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' + # FileIdBothDirectoryInformation structure. See [MS-SMB]: 2.2.8.1.3 and [MS-FSCC]: 2.4.17 + info_format = '<IIQQQQQQIIIBB24sHQ' info_size = struct.calcsize(info_format) data_length = len(data_bytes) @@ -679,17 +746,24 @@ 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]) + short_name_length, _, short_name, _, file_id = struct.unpack(info_format, data_bytes[offset:offset+info_size]) offset2 = offset + info_size if offset2 + filename_length > data_length: 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)) + short_name = short_name[:short_name_length].decode('UTF-16LE') + + accept_result = False + if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc + accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL) + else: + accept_result = (file_attributes & search) > 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename, file_id)) if next_offset: offset += next_offset @@ -708,7 +782,11 @@ 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 kwargs['error'] == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) if not self.connected_trees.has_key(service_name): def connectCB(connect_message, **kwargs): @@ -758,17 +836,18 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: p = create_message.payload + filename = self._extractLastPathComponent(unicode(path)) 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) + filename, filename) + closeFid(kwargs['tid'], p.fid, info = info) else: errback(OperationFailure('Failed to get attributes for %s on %s: Unable to open remote file object' % ( path, service_name ), messages_history)) @@ -793,6 +872,87 @@ 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 _getSecurity_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 = [ ] + results = [ ] + + def sendCreate(tid): + 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 | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + oplock = SMB2_OPLOCK_LEVEL_NONE, + impersonation = SEC_IMPERSONATE, + create_options = 0, + create_disp = FILE_OPEN)) + 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 = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + info_type = SMB2_INFO_SECURITY, + file_info_class = 0, # [MS-SMB2] 2.2.37, 3.2.4.12 + input_buf = '', + output_buf_len = self.max_transact_size)) + m.tid = kwargs['tid'] + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) + messages_history.append(m) + else: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to open file or directory' % ( path, service_name ), messages_history)) + + def queryCB(query_message, **kwargs): + messages_history.append(query_message) + if query_message.status == 0: + security = SecurityDescriptor.from_bytes(query_message.payload.data) + closeFid(kwargs['tid'], kwargs['fid'], result = security) + else: + closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status) + + def closeFid(tid, fid, result = 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, result = result, error = error) + messages_history.append(m) + + def closeCB(close_message, **kwargs): + if kwargs['result'] is not None: + callback(kwargs['result']) + elif kwargs['error'] is not None: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) + + 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 the security descriptor 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) @@ -829,7 +989,7 @@ 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, + share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, oplock = SMB2_OPLOCK_LEVEL_NONE, impersonation = SEC_IMPERSONATE, create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE, @@ -850,13 +1010,15 @@ file_info_class = 0x16, # FileStreamInformation [MS-FSCC] 2.4 input_buf = '', output_buf_len = 4096)) - m.tid = create_message.tid + m.tid = kwargs['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) + tid = kwargs['tid'], + 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)) + errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history)) def infoCB(info_message, **kwargs): messages_history.append(info_message) @@ -871,9 +1033,9 @@ 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)) + sendRead(kwargs['tid'], kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes']) + else: + errback(OperationFailure('Failed to retrieve %s on %s: Unable to retrieve information on file' % ( path, service_name ), messages_history)) def sendRead(tid, fid, offset, remaining_len, read_len, file_attributes): read_count = min(self.max_read_size, remaining_len) @@ -881,7 +1043,7 @@ m.tid = tid self._sendSMBMessage(m) self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, - fid = fid, offset = offset, + tid = tid, fid = fid, offset = offset, remaining_len = remaining_len, read_len = read_len, file_attributes = file_attributes) @@ -895,12 +1057,12 @@ 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 )) + sendRead(kwargs['tid'], kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes']) + else: + closeFid(kwargs['tid'], kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len )) else: messages_history.append(read_message) - closeFid(read_message.tid, kwargs['fid'], error = read_message.status) + closeFid(kwargs['tid'], kwargs['fid'], error = read_message.status) def closeFid(tid, fid, ret = None, error = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -938,6 +1100,7 @@ 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:] @@ -971,6 +1134,7 @@ messages_history.append(m) def createCB(create_message, **kwargs): + create_message.tid = kwargs['tid'] messages_history.append(create_message) if create_message.status == 0: sendWrite(create_message.tid, create_message.payload.fid, starting_offset) @@ -985,17 +1149,17 @@ 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, tid = tid, fid = fid, offset = offset+data_len) else: closeFid(tid, fid, offset = offset) def writeCB(write_message, **kwargs): # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history. if write_message.status == 0: - sendWrite(write_message.tid, kwargs['fid'], kwargs['offset']) + sendWrite(kwargs['tid'], kwargs['fid'], kwargs['offset']) else: messages_history.append(write_message) - closeFid(write_message.tid, kwargs['fid']) + closeFid(kwargs['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): @@ -1028,16 +1192,99 @@ sendCreate(self.connected_trees[service_name]) - def _deleteFiles_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB2(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') expiry_time = time.time() + timeout + pattern = None path = path_file_pattern.replace('/', '\\') if path.startswith('\\'): path = path[1:] if path.endswith('\\'): path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB2__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB2(service_name, p, deleteCB, errback, timeout) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + if 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 + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB2__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB2(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB2(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + def _deleteFiles_SMB2__del(self, service_name, tid, path, callback, errback, timeout = 30): messages_history = [ ] def sendCreate(tid): @@ -1064,9 +1311,14 @@ messages_history.append(m) def createCB(open_message, **kwargs): + open_message.tid = kwargs['tid'] messages_history.append(open_message) if open_message.status == 0: sendDelete(open_message.tid, open_message.payload.fid) + elif open_message.status == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_NOT_FOUND + callback(path) + elif open_message.status == 0xC00000BAL: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) else: errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -1076,22 +1328,18 @@ 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) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4.11 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1102,25 +1350,11 @@ def closeCB(close_message, **kwargs): if kwargs['status'] == 0: - callback(path_file_pattern) + 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]) + sendCreate(tid) def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): if not self.has_authenticated: @@ -1161,7 +1395,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendReset(open_message.tid, open_message.payload.fid) + sendReset(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -1171,24 +1405,18 @@ 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) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4, [MS-FSCC]: 2.4.7, [MS-FSCC]: 2.6 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = reset_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1251,13 +1479,13 @@ 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) + 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: - closeFid(create_message.tid, create_message.payload.fid) + closeFid(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history)) @@ -1325,7 +1553,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) + sendDelete(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) @@ -1337,15 +1565,15 @@ data = '\x01')) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1421,7 +1649,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendRename(create_message.tid, create_message.payload.fid) + sendRename(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to rename %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1434,15 +1662,15 @@ data = data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = rename_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1509,7 +1737,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendEnumSnapshots(create_message.tid, create_message.payload.fid) + sendEnumSnapshots(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1650,9 +1878,38 @@ 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 + elif (message.status.internal_value == 0xc000006d # STATUS_LOGON_FAILURE + or message.status.internal_value == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status.internal_value == 0xc000006a): # STATUS_WRONG_PASSWORD self.has_authenticated = False - self.log.info('Authentication (with extended security) failed. Please check username and password. You may need to enable/disable NTLMv2 authentication.') + self.log.info('Authentication (with extended security) failed. Please check username and password.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status.internal_value == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status.internal_value == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status.internal_value == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Not allowed.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Workstation not trusted.') self.onAuthFailed() else: raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status.internal_value, @@ -1719,7 +1976,8 @@ lm_challenge_response, session_key, self.username, - self.domain) + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -1913,13 +2171,12 @@ 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]) + sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) else: closeFid(read_message.tid, kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) @@ -1958,15 +2215,15 @@ 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 + search & 0xFFFF, # SearchAttributes (need to restrict the values due to introduction of SMB_FILE_ATTRIBUTE_INCL_NORMAL) 100, # SearchCount 0x0006, # Flags: SMB_FIND_CLOSE_AT_EOS | SMB_FIND_RETURN_RESUME_KEYS 0x0104, # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO - 0x0000) # SearchStorageType + 0x0000) # SearchStorageType (seems to be ignored by Windows) if support_dfs: params_bytes += ("\\" + self.remote_name + "\\" + service_name + path + pattern + '\0').encode('UTF-16LE') else: - params_bytes += (path + pattern).encode('UTF-16LE') + params_bytes += (path + pattern + '\0').encode('UTF-16LE') m = SMBMessage(ComTransaction2Request(max_params_count = 10, max_data_count = 16644, @@ -2002,9 +2259,16 @@ 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)) + + accept_result = False + if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc + accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL) + else: + accept_result = (file_attributes & search) > 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename)) if next_offset: offset += next_offset @@ -2044,11 +2308,15 @@ 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): + sendFindNext(find_message.tid, sid, 0, results[-1].filename, kwargs.get('support_dfs', False)) + else: + if find_message.status.internal_value == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history)) + + def sendFindNext(tid, sid, resume_key, resume_file, 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', @@ -2056,11 +2324,8 @@ 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') + 0x0006) # Flags: SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS + params_bytes += (resume_file+'\0').encode('UTF-16LE') m = SMBMessage(ComTransaction2Request(max_params_count = 10, max_data_count = 16644, @@ -2106,7 +2371,7 @@ elif end_of_search: callback(results) else: - sendFindNext(find_message.tid, kwargs['sid'], last_name_offset, kwargs.get('support_dfs', False)) + sendFindNext(find_message.tid, kwargs['sid'], 0, results[-1].filename, kwargs.get('support_dfs', False)) else: errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history)) @@ -2184,9 +2449,10 @@ 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)) + filename = self._extractLastPathComponent(unicode(path)) + + info = SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, filename, filename) callback(info) else: errback(OperationFailure('Failed to get attributes for %s on %s: Read failed' % ( path, service_name ), messages_history)) @@ -2206,6 +2472,9 @@ messages_history.append(m) else: sendQuery(self.connected_trees[service_name]) + + def _getSecurity_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): + raise NotReadyError('getSecurity is not yet implemented for SMB1') def _retrieveFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30): return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0L, -1L, timeout) @@ -2374,11 +2643,100 @@ else: sendOpen(self.connected_trees[service_name]) - def _deleteFiles_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB1(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') + expiry_time = time.time() + timeout + pattern = None path = path_file_pattern.replace('/', '\\') + if path.startswith('\\'): + path = path[1:] + if path.endswith('\\'): + path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB1__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB1(service_name, p, deleteCB, errback, timeout = 30) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + 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 files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB1__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB1(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB1(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + + def _deleteFiles_SMB1__del(self, service_name, tid, path, callback, errback, timeout = 30): messages_history = [ ] def sendDelete(tid): @@ -2392,9 +2750,79 @@ def deleteCB(delete_message, **kwargs): messages_history.append(delete_message) if not delete_message.status.hasError: + callback(path) + elif delete_message.status.internal_value == 0xC000000FL: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing callback(path_file_pattern) - else: - errback(OperationFailure('Failed to store %s on %s: Delete failed' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC00000BAL: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to delete %s on %s: Path not found' % ( path, service_name ), messages_history)) + else: + errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) + + sendDelete(tid) + + 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): @@ -2403,84 +2831,7 @@ 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)) + errback(OperationFailure('Failed to delete 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) @@ -2615,6 +2966,9 @@ def _echo_SMB1(self, data, callback, errback, timeout = 30): messages_history = [ ] + if not isinstance(data, type(b'')): + raise TypeError('Echo data must be %s not %s' % (type(b'').__name__, type(data).__name__)) + def echoCB(echo_message, **kwargs): messages_history.append(echo_message) if not echo_message.status.hasError: @@ -2627,10 +2981,18 @@ self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback) messages_history.append(m) + def _extractLastPathComponent(self, path): + return path.replace('\\', '/').split('/')[-1] +
    [docs]class SharedDevice: """ Contains information about a single shared device on the remote server. + + The following attributes are available: + + * name : An unicode string containing the name of the shared device + * comments : An unicode string containing the user description of the shared device """ # The following constants are taken from [MS-SRVS]: 2.2.2.4 @@ -2686,18 +3048,32 @@ 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). + + The following attributes are available: + + * create_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server + * last_access_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server + * last_write_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server + * last_attr_change_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server + * file_size : File size in number of bytes + * alloc_size : Total number of bytes allocated to store this file + * file_attributes : A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. + * short_name : Unicode string containing the short name of this file (usually in 8.3 notation) + * filename : Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + * file_id : Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 """ - def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename): + def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename, file_id=None): self.create_time = create_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server self.last_access_time = last_access_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server self.last_write_time = last_write_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server self.last_attr_change_time = last_attr_change_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server self.file_size = file_size #: File size in number of bytes self.alloc_size = alloc_size #: Total number of bytes allocated to store this file - self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3 + self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. self.short_name = short_name #: Unicode string containing the short name of this file (usually in 8.3 notation) self.filename = filename #: Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + self.file_id = file_id #: Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 @property def isDirectory(self): @@ -2708,6 +3084,16 @@ def isReadOnly(self): """A convenient property to return True if this file resource is read-only on the remote server""" return bool(self.file_attributes & ATTR_READONLY) + + @property + def isNormal(self): + """ + A convenient property to return True if this is a normal file. + + Note that pysmb defines a normal file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + """ + return (self.file_attributes==ATTR_NORMAL) or ((self.file_attributes & 0xff)==0) def __unicode__(self): return u'Shared file: %s (FileSize:%d bytes, isDirectory:%s)' % ( self.filename, self.file_size, self.isDirectory )
    @@ -2734,12 +3120,15 @@
  • index
  • - +
  • + modules |
  • + diff --git a/docs/html/_modules/smb/security_descriptors.html b/docs/html/_modules/smb/security_descriptors.html new file mode 100644 index 0000000..f740ca7 --- /dev/null +++ b/docs/html/_modules/smb/security_descriptors.html @@ -0,0 +1,459 @@ + + + + + + + + smb.security_descriptors — pysmb 1.2.1 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..b8423a8 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.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -33,7 +33,10 @@
  • index
  • - +
  • + modules |
  • + @@ -1343,7 +1346,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 +1497,15 @@
  • index
  • - +
  • + modules |
  • + diff --git a/docs/html/_sources/api/smb_SMBHandler.txt b/docs/html/_sources/api/smb_SMBHandler.txt index da106fe..9300de1 100644 --- a/docs/html/_sources/api/smb_SMBHandler.txt +++ b/docs/html/_sources/api/smb_SMBHandler.txt @@ -6,8 +6,12 @@ Notes ----- -* Note that you need to pass in a valid hostname or IP address for the host component of the URL. - Do not use the Windows/NetBIOS machine name for the host component. +* The host component of the URL must be one of the following: + + * A fully-qualified hostname that can be resolved by your local DNS service. Example: myserver.test.com + * An IP address. Example: 192.168.1.1 + * A comma-separated string "," where ** is the Windows/NetBIOS machine name for remote SMB service, and ** is the service's IP address. Example: MYSERVER,192.168.1.1 + * The first component of the path in the URL points to the name of the shared folder. Subsequent path components will point to the directory/folder of the file. * You can retrieve and upload files, but you cannot delete files/folders or create folders. @@ -16,7 +20,7 @@ Example ------- -The following code snippet illustrates file retrieval.:: +The following code snippet illustrates file retrieval with Python 2.:: # -*- coding: utf-8 -*- import urllib2 @@ -34,7 +38,7 @@ # Process fh2 like a file-like object and then close it. fh2.close() -The following code snippet illustrates file upload. You need to provide a file-like object for the *data* parameter in the *open()* method:: +The following code snippet illustrates file upload with Python 2. You need to provide a file-like object for the *data* parameter in the *open()* method:: import urllib2 from smb.SMBHandler import SMBHandler @@ -46,3 +50,34 @@ # Reading from fh will only return an empty string fh.close() + + +The following code snippet illustrates file retrieval with Python 3.:: + + import urllib + from smb.SMBHandler import SMBHandler + + director = urllib.request.build_opener(SMBHandler) + fh = director.open('smb://myuserID:mypassword@192.168.1.1/sharedfolder/rfc1001.txt') + + # Process fh like a file-like object and then close it. + fh.close() + + # For paths/files with unicode characters, simply pass in the URL as an unicode string + fh2 = director.open(u'smb://myuserID:mypassword@192.168.1.1/sharedfolder/测试文件夹/垃圾文件.dat') + + # Process fh2 like a file-like object and then close it. + fh2.close() + +The following code snippet illustrates file upload with Python 3. You need to provide a file-like object for the *data* parameter in the *open()* method:: + + import urllib + from smb.SMBHandler import SMBHandler + + file_fh = open('local_file.dat', 'rb') + + director = urllib.request.build_opener(SMBHandler) + fh = director.open('smb://myuserID:mypassword@192.168.1.1/sharedfolder/upload_file.dat', data = file_fh) + + # Reading from fh will only return an empty string + fh.close() 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/_sources/index.txt b/docs/html/_sources/index.txt index 7638fcd..e3eb1ac 100644 --- a/docs/html/_sources/index.txt +++ b/docs/html/_sources/index.txt @@ -8,9 +8,9 @@ pysmb is a pure Python implementation of the client-side SMB/CIFS protocol (SMB1 and SMB2) which is the underlying protocol that facilitates file sharing and printing between Windows machines, as well as with Linux machines via the Samba server application. -pysmb is developed in Python 2.4.6, Python 2.7.1 and Python 3.2.3 and has been tested against shared folders on Windows XP SP3, Windows Vista, Windows 7 and Samba 3.x. +pysmb is developed in Python 2.7.x and Python 3.5.x and has been tested against shared folders on Windows XP SP3, Windows Vista, Windows 7 and Samba 3.x. -The latest version of pysmb is always available at the pysmb project page at `miketeo.net `_. +The latest version of pysmb is always available at the pysmb project page at `miketeo.net `_. License ------- @@ -90,6 +90,8 @@ As a software developer who is looking to modify pysmb so that you can integrate it to other network frameworks: * Read :doc:`extending` +If you are upgrading from older pysmb versions: + * Read :doc:`upgrading` Indices and tables @@ -101,6 +103,7 @@ api/* extending + upgrading * :ref:`genindex` * :ref:`search` diff --git a/docs/html/_sources/upgrading.txt b/docs/html/_sources/upgrading.txt new file mode 100644 index 0000000..8d5e7cd --- /dev/null +++ b/docs/html/_sources/upgrading.txt @@ -0,0 +1,63 @@ +Upgrading from older pysmb versions +==================================== + +This page documents the improvements and changes to the API that could be incompatible with previous releases. + +pysmb 1.2.0 +----------- +- Add new `delete_matching_folders` parameter to `deleteFiles()` method in SMBProtocolFactory and SMBConnection + class to support deletion of sub-folders. If you are passing timeout parameter to the `deleteFiles()` method + in your application, please switch to using named parameter for timeout. + +pysmb 1.1.28 +------------ +- SharedFile instances returned from the `listPath()` method now has a new property + `file_id` attribute which represents the file reference number given by the remote SMB server. + +pysmb 1.1.26 +------------ +- SMBConnection class can now be used as a context manager + +pysmb 1.1.25 +------------ +- SharedFile class has a new property `isNormal` which will be True if the file is a + 'normal' file. 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 in SMBProtocolFactory and SMBConnection class will now include + 'normal' files by default if you do not specify the `search` parameter. + +pysmb 1.1.20 +------------ +- A new method `getSecurity()` was added to SMBConnection and SMBProtocolFactory class. + +pysmb 1.1.15 +------------ +- Add new `truncate` parameter to `storeFileFromOffset()` in SMBProtocolFactory and SMBConnection + class to support truncation of the file before writing. If you are passing timeout parameter + to the `storeFileFromOffset()` method in your application, please switch to using named parameter for timeout. + +pysmb 1.1.11 +------------ +- A new method `storeFileFromOffset()` was added to SMBConnection and SMBProtocolFactory class. + +pysmb 1.1.10 +------------ +- A new method `getAttributes()` was added to SMBConnection and SMBProtocolFactory class +- SharedFile class has a new property `isReadOnly` to indicate the file is read-only on the remote filesystem. + +pysmb 1.1.2 +----------- +- `queryIPForName()` method in nmb.NetBIOS and nmb.NBNSProtocol class will now return only the server machine name and ignore workgroup names. + +pysmb 1.0.3 +----------- +- Two new methods were added to NBNSProtocol class: `queryIPForName()` and `NetBIOS.queryIPForName()` + to support querying for a machine's NetBIOS name at the given IP address. +- A new method `retrieveFileFromOffset()` was added to SMBProtocolFactory and SMBConnection + to support finer control of file retrieval operation. + +pysmb 1.0.0 +----------- +pysmb was completely rewritten in version 1.0.0. +If you are upgrading from pysmb 0.x, you most likely have to rewrite your application for the new 1.x API. diff --git a/docs/html/api/nmb_NBNSProtocol.html b/docs/html/api/nmb_NBNSProtocol.html index 263abd9..0ecd94d 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.2.1 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..dfb735b 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.2.1 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..23f85cf 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.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -35,12 +35,15 @@ index
  • + modules |
  • +
  • next |
  • previous |
  • - + diff --git a/docs/html/index.html b/docs/html/index.html index 614879b..f45b985 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -6,7 +6,7 @@ - Welcome to pysmb’s documentation! — pysmb 1.1.18 documentation + Welcome to pysmb’s documentation! — pysmb 1.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -34,9 +34,12 @@ index
  • + modules |
  • +
  • next |
  • - + diff --git a/docs/html/objects.inv b/docs/html/objects.inv index 86b3538..184a3ca 100644 Binary files a/docs/html/objects.inv and b/docs/html/objects.inv differ diff --git a/docs/html/py-modindex.html b/docs/html/py-modindex.html new file mode 100644 index 0000000..9256f5d --- /dev/null +++ b/docs/html/py-modindex.html @@ -0,0 +1,114 @@ + + + + + + + + Python Module Index — pysmb 1.2.1 documentation + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + +

    Python Module Index

    + +
    + s +
    + + + + + + + + + + +
     
    + s
    + smb +
        + smb.security_descriptors + Data structures used in Windows security descriptors.
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/html/search.html b/docs/html/search.html index 1e29df4..dcb4835 100644 --- a/docs/html/search.html +++ b/docs/html/search.html @@ -6,7 +6,7 @@ - Search — pysmb 1.1.18 documentation + Search — pysmb 1.2.1 documentation @@ -14,7 +14,7 @@ - + @@ -40,7 +40,10 @@
  • index
  • - +
  • + modules |
  • + diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index 0cbc511..bdb92a0 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({envversion:46,filenames:["api/nmb_NBNSProtocol","api/nmb_NetBIOS","api/smb_SMBConnection","api/smb_SMBHandler","api/smb_SMBProtocolFactory","api/smb_SharedDevice","api/smb_SharedFile","api/smb_exceptions","extending","index"],objects:{"nmb.NetBIOS":{NetBIOS:[1,0,1,""]},"nmb.NetBIOS.NetBIOS":{"__init__":[1,1,1,""],close:[1,1,1,""],queryIPForName:[1,1,1,""],queryName:[1,1,1,""]},"nmb.NetBIOSProtocol":{NBNSProtocol:[0,0,1,""],NetBIOSTimeout:[0,0,1,""]},"nmb.NetBIOSProtocol.NBNSProtocol":{"__init__":[0,1,1,""],queryIPForName:[0,1,1,""],queryName:[0,1,1,""]},"smb.SMBConnection":{SMBConnection:[2,0,1,""]},"smb.SMBConnection.SMBConnection":{"__init__":[2,1,1,""],SIGN_NEVER:[2,2,1,""],SIGN_WHEN_REQUIRED:[2,2,1,""],SIGN_WHEN_SUPPORTED:[2,2,1,""],close:[2,1,1,""],connect:[2,1,1,""],createDirectory:[2,1,1,""],deleteDirectory:[2,1,1,""],deleteFiles:[2,1,1,""],echo:[2,1,1,""],getAttributes:[2,1,1,""],isUsingSMB2:[2,2,1,""],listPath:[2,1,1,""],listShares:[2,1,1,""],listSnapshots:[2,1,1,""],rename:[2,1,1,""],resetFileAttributes:[2,1,1,""],retrieveFile:[2,1,1,""],retrieveFileFromOffset:[2,1,1,""],storeFile:[2,1,1,""],storeFileFromOffset:[2,1,1,""]},"smb.SMBProtocol":{SMBProtocolFactory:[4,0,1,""]},"smb.SMBProtocol.SMBProtocolFactory":{"__init__":[4,1,1,""],SIGN_NEVER:[4,2,1,""],SIGN_WHEN_REQUIRED:[4,2,1,""],SIGN_WHEN_SUPPORTED:[4,2,1,""],closeConnection:[4,1,1,""],createDirectory:[4,1,1,""],deleteDirectory:[4,1,1,""],deleteFiles:[4,1,1,""],echo:[4,1,1,""],getAttributes:[4,1,1,""],instance:[4,2,1,""],isReady:[4,2,1,""],isUsingSMB2:[4,2,1,""],listPath:[4,1,1,""],listShares:[4,1,1,""],listSnapshots:[4,1,1,""],onAuthFailed:[4,1,1,""],onAuthOK:[4,1,1,""],rename:[4,1,1,""],retrieveFile:[4,1,1,""],retrieveFileFromOffset:[4,1,1,""],storeFile:[4,1,1,""]},"smb.base":{NotConnectedError:[7,0,1,""],NotReadyError:[7,0,1,""],SMBTimeout:[7,0,1,""],SharedDevice:[5,0,1,""],SharedFile:[6,0,1,""]},"smb.base.SharedDevice":{comments:[5,2,1,""],isSpecial:[5,2,1,""],isTemporary:[5,2,1,""],name:[5,2,1,""],type:[5,2,1,""]},"smb.base.SharedFile":{alloc_size:[6,2,1,""],create_time:[6,2,1,""],file_attributes:[6,2,1,""],file_size:[6,2,1,""],filename:[6,2,1,""],isDirectory:[6,2,1,""],isReadOnly:[6,2,1,""],last_access_time:[6,2,1,""],last_attr_change_time:[6,2,1,""],last_write_time:[6,2,1,""],short_name:[6,2,1,""]},"smb.smb_structs":{OperationFailure:[7,0,1,""],ProtocolError:[7,0,1,""],UnsupportedFeature:[7,0,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute"},terms:{"3rd":9,"60kbyte":4,"\u5783\u573e\u6587\u4ef6":3,"\u6d4b\u8bd5\u6587\u4ef6\u5939":3,"__init__":[0,1,2,4,8],"abstract":9,"boolean":[0,1,2,4],"byte":[2,4,6],"case":4,"default":[2,4],"float":[0,1,4,6],"function":[0,1,2,4,9],"import":[2,3,4,9],"int":[2,4],"long":[2,4,6],"new":[1,2,4,8],"public":4,"return":[0,1,2,3,4,5,6,9],"short":6,"true":[0,1,2,4,5,6],"try":[2,4,8],"while":7,aaa:[0,1],about:[2,4,5,6],abov:8,accept:[4,8],access:[2,4,6],accur:4,acknowledg:4,across:[2,4],actual:2,add:[0,4],addcallback:[0,4],adderrback:4,address:[0,1,3,8,9],admin:5,administr:5,after:[0,1,2,4,9],again:0,against:9,algorithm:[2,4,8,9],all:[2,4,9],alloc:6,alloc_s:6,allow:[0,2,4],alon:9,alphanumer:[2,4],alreadi:2,also:[2,4,5],alwai:9,anderson:[0,1],ani:[1,2,4,6,9],appli:[2,4],applic:[0,1,4,6,9],appropri:[0,1],arbitari:[2,4],archiv:[2,4],arg:4,around:0,ascii:[2,4],asn:9,aspx:2,assert:2,asynchron:[0,2,8],attempt:2,attribut:[2,4,6],atttempt:2,auth:4,authent:[2,4,7,8,9],authenthent:4,auto:[2,4],automat:[0,1,2,4],avail:[2,4,9],avoid:4,back:[0,1,2,4],base:[2,4,5,6,7,8,9],batch:4,bbb:[0,1],been:[2,4,7,8,9],befor:[2,4],begin:[2,4],between:9,bind:[0,1],bit:[2,4,9],bitwis:[2,4],blob:9,block:[0,1,2,9],both:[2,4],briefli:8,broadcast:[0,1],buffer:8,build_open:3,busi:[2,4],call:[0,1,2,4,6,8],callback:[0,4,8],can:[0,1,2,3,4,5,8,9],cannot:[2,3,4],captur:[2,4],care:8,cc232110:2,ccc:[0,1],chang:6,charact:[2,3,4,6],choic:[2,4],choos:[2,4],cif:[1,2,4,6,7,8,9],clear:2,click:[2,4],client:[2,4,9],client_machine_nam:[2,4],close:[1,2,3,4],closeconnect:4,code:3,com:2,come:9,comm_devic:5,command:[2,4],comment:5,commerci:9,commun:[2,4,5,9],compat:[2,4],complet:[2,4,7,9],compon:[2,3,4],comput:[2,4],concurr:[2,4],configur:[2,4],conflict:9,conn:2,connect:[2,4,7,8],connecttcp:4,constant:[5,9],constructor:8,contain:[1,2,4,5,6,9],content:[2,4],continu:[2,4],contribut:[0,1],control:[2,4],conveni:[2,4,6],copi:[2,4,9],correspond:6,could:[6,9],cover:9,creat:[0,1,2,3,4,8],create_tim:6,createdirectori:[2,4],creation:[5,6],credenti:[2,4],credienti:4,current:[2,7],dat:3,data:[2,3,4,8,9],data_buf:7,datetim:[2,4],ddd:[0,1],decod:9,def:4,defer:[0,4],defin:[0,1],delet:[2,3,4],deletedirectori:[2,4],deletefil:[2,4],depend:9,describ:[2,8],descript:5,detail:[6,8,9],detect:[2,4],determin:[0,1,2,4,9],develop:[2,6,9],devic:[5,6],dialect:9,differ:[2,4,8,9],digest:9,direct:[2,4],directli:[4,6],director:3,directori:[2,3,4,6],disabl:[2,4],disconnect:[2,4,7],discret:9,disk_tre:5,dixon:9,dmitri:9,doe:[2,4,6,9],domain:[2,4,9],done:[0,1],dot:[0,1],download:[4,9],due:8,each:[1,2,4,5,6],earliest:4,echo:[2,4],edit:[2,4],either:[2,4],element:[2,4],els:[2,4],empti:[0,1,2,3,4,6],enabl:[2,4],encod:9,encount:[2,6],encrypt:9,end:[2,4],english:[2,4],enterpris:[2,4],entir:4,entri:6,eof:[2,4],errback:[0,4],error:[2,4,8],establish:[2,4],etc:4,excee:4,except:[0,4],exercis:9,exist:[2,3,4],expos:4,facilit:9,factori:4,fail:[2,4,7,8],failur:0,fallback:[2,4],fals:[0,1,2,4],familiar:9,featur:[2,4,7],feeddata:8,fh2:3,file:[2,3,4,5,6,8,9],file_attribut:[2,4,6],file_attribute_norm:2,file_fh:3,file_obj:[2,4],file_s:[4,6],filenam:[2,4,6],fileretriev:4,files:2,filter:[2,4],find:[2,4,8],first:[2,3,4],flag:[0,1,2,4],folder:[2,3,4,6,9],follow:[2,3,4,5,9],forth:5,framework:[0,4],free:[0,1,9],freeli:[2,4],from:[0,1,2,3,4,8,9],functionl:4,further:2,gener:8,getattribut:[2,4],gmt:[2,4],googl:9,guess:[2,4],handl:[4,8],hash:9,have:[0,2,8,9],held:2,help:2,henc:[2,4,9],hidden:[2,4],hope:[0,1],host:[2,3,4],hostnam:3,http:2,iana:[0,1],identifi:[2,4],idl:2,illustr:[2,3,4],immedi:[2,4],implement:[0,1,2,4,8,9],impos:[2,4],includ:[8,9],incom:0,incomplet:8,index:9,indic:[0,1,2,4],individu:4,inform:[2,4,5,6],initi:[5,8],insid:[2,4],instal:[2,4,9],instanc:[0,1,2,4,6],instanti:[0,1,4,6],instiant:[0,1],integ:[0,1,2,4,6,9],integr:[5,9],interest:[2,4],intern:[4,8,9],internet:[0,4],interprocess:5,interv:4,invok:[2,4],involv:8,ipc:5,ipv4:1,is_direct_tcp:[2,4],isdirectori:6,isreadi:4,isreadonli:6,isspeci:5,istemporari:5,isusingsmb2:[2,4],itself:[4,9],jason:[0,1],just:[0,4],keep:2,keepal:2,know:[0,1,4],known:[2,4],kwarg:4,last:6,last_access_tim:6,last_attr_change_tim:6,last_write_tim:6,latest:9,learn:[2,4],least:2,leav:[0,1,2,4],length:6,lgpl:9,librari:[2,9],like:[2,3,4],limit:[2,4,6],linux:9,list:[0,1,2,4,9],listen:[0,1],listen_port:[0,1],listenudp:0,listpath:[2,4,6],listshar:[2,4],listsnapshot:[2,4],local:[2,4],local_fil:3,look:9,loop:8,loseconnect:4,machin:[0,1,2,3,4,9],made:[2,4,9],mai:[2,4],main:9,match:[0,1,2,4],max_length:[2,4],maximum:[2,4],md4:9,mean:4,meant:2,mechan:[2,4],messag:[2,4,7,9],method:[0,1,2,3,4,6,8],microsoft:[2,9],might:[2,4],miketeo:9,mit:9,mode:[0,1],modif:6,modifi:9,modul:[2,4,9],more:[2,4,6,8,9],most:[2,4,8,9],msdn:2,multipl:[2,4],must:[0,2,4],my_nam:[2,4],mypassword:3,myuserid:3,name:[0,1,2,3,4,5,6,9],namedtemporaryfil:[2,4],nbn:[1,9],need:[2,3,4,6,8,9],neg:[2,4],net:9,netbio:0,netbiosprotocol:[0,9],netbiossess:9,netbiostimeout:0,network:[0,1,2,4,9],never:[2,4],new_path:[2,4],newer:[2,4],next:2,nmb:[0,1,9],non:[2,4],none:[0,1,2,4,5,6,7],notat:[0,1,6],notconnectederror:[4,7],note:2,notreadyerror:[4,7],now:8,ntlm:[4,8,9],ntlmssp:9,ntlmv1:[2,4,9],ntlmv2:[2,4,9],number:[0,1,2,4,6],obj:[2,4],object:[2,3,4],occur:[2,4,7],offset:[2,4],often:8,old:[2,4],old_path:[2,4],onauthfail:[4,8],onauthok:[4,8],onc:[4,8],onli:[2,3,4,6,9],onnmbsessionfail:8,open:[2,3,4,9],opensourc:9,oper:[1,2,4,7,8,9],operationfailur:[2,4,7],opportun:4,organ:9,origin:[2,4,6],other:[2,4],otherwis:[2,4],out:[2,4],over:[2,4,9],overrid:[4,8],overwritten:[2,4],own:[4,8,9],packag:3,packet:[0,1,4,8],page:[8,9],paramet:[0,1,2,3,4,8,9],parent:3,pars:9,part:8,parti:9,pass:[2,3,4],password:[2,4],path:[2,3,4],path_file_pattern:[2,4],pathnam:[2,4],pattern:[2,4],perform:[0,1,2,4,8],period:4,persist:5,place:9,pleas:9,point:3,port:[0,1,2,4],posit:[2,4],possibl:9,post:[4,8],precis:4,present:7,print:[4,9],print_queu:5,proce:4,proceed:[2,4],process:[3,8],prohibit:6,project:[0,4,9],promis:9,properti:[2,4,6],protocol:[2,4,7,9],protocolerror:7,provid:[0,1,2,3,4,8,9],pure:9,purpos:9,pyasn1:9,pyde:9,pymsb:4,pysmb:[0,2,4,7],python:[2,3,4,9],queri:[0,1,2,4,9],queryipfornam:[0,1],querynam:[0,1],queue:4,rais:[0,2,3,4,7],reactor:[0,4],read:[2,3,4,6,8,9],readi:[4,7],receiv:[0,1,2,4],refer:[2,4,5,9],referenc:9,regardless:[2,4],regular:[2,4],reject:[2,4],rel:[2,4],relat:9,releas:[1,2],remot:[1,2,4,5,6,8],remote_nam:[2,4,8],remov:0,renam:[2,4],repli:[0,1,2,4],report:8,request:[2,4],requir:[2,4,7],reserv:5,reset:2,resetfileattribut:2,resourc:[1,2,4,6],respond:2,respons:7,result:[0,2,4],retri:4,retriev:[2,3,4],retrievefil:[2,4],retrievefilefactori:4,retrievefilefromoffset:[2,4],reus:4,rfc1001:[2,3,4],right:[2,4],routin:9,rozmanov:9,safe:[2,4],samba:9,sambda:[2,4],same:[2,4],search:[2,4,9],sec:4,second:[0,1,4,6],section:9,secur:9,securityblob:9,see:[2,4,6],seek:[2,4],select:[0,1,2,4],self:[4,8],send:[0,1,2,4],separ:9,sequenti:2,seri:4,server:[2,4,5,6,8,9],server_ip:[2,4],server_nam:[2,4],servic:[1,2,4,8,9],service_nam:[2,4],session:[8,9],set:[2,4],setup:[0,1,4,8],sha256:9,sha:9,shadow:[2,4],share:[2,3,4,5,6,9],shareddevic:[2,4],sharedfil:[2,4],sharedfold:3,short_nam:6,should:[0,1,2,4,6],side:9,sign:[2,4],sign_nev:[2,4],sign_opt:[2,4],sign_when_requir:[2,4],sign_when_support:[2,4],simpl:[2,4],simpli:3,sinc:6,singl:[2,4,5],site:9,size:[4,6],smb1:[2,4,9],smb2_constant:9,smb2_struct:9,smb:[1,2,3,4,5,6],smb_constant:[2,4,9],smb_ext_file_attr:6,smb_file_attribute_xxx:[2,4],smb_messag:7,smb_struct:[2,4,7,9],smbprotocol:[4,6,9],smbtest:[2,4],smbtimeout:[4,7],snapshot:[2,4],snippet:3,sock_famili:2,socket:[1,2,8],softwar:9,some:[2,4,9],sort:2,sourc:[0,1,2,4,5,6,7,9],sp3:9,space:[2,4],special:5,specif:9,specifi:[0,1,2,4],standard:[0,1,9],start:[0,2,4],step:8,stoplisten:0,store:[2,4,6],storefil:[2,4],storefilefromoffset:2,string:[0,1,2,3,4,5,6],style:9,sub:[2,4],subclass:[4,8],subsequ:3,success:[2,8],successfulli:[2,4],suitabl:[2,9],support_smb2:[2,4],synchron:[8,9],system:[2,4],take:[8,9],target:[0,1],tcp:[2,4,8],technic:[2,8],tempfil:[2,4],temporari:5,term:9,termin:[2,4],test:9,than:[2,4],thei:9,therefor:2,thi:[0,1,2,4,5,6,8,9],thoma:9,those:4,thousand:4,through:4,time:[2,4,5,6,9],timeout:[0,1,2,4,7],todd:9,togeth:9,too:2,total:6,touch:[0,1,4],transfer:[4,9],translat:9,transmit:4,transport:[0,4,9],truncat:2,tupl:[2,4],twist:[0,4,9],txt:[2,3,4],type:5,u32:9,udp:[0,1],ultim:[2,4],under:9,underli:[1,2,4,7,9],unicod:[2,3,4,5,6],unless:[0,1],unlock:2,unsign:9,unsupportedfeatur:7,until:[1,2,4],upload:[2,3,4],upload_fil:3,url:[3,9],urlerror:3,urllib2:[3,9],use_ntlm_v2:[2,4,8],user:[2,4,5],userid:[2,4],usernam:[2,4],usual:[2,4,6,8,9],utc:[2,4],utf:3,util:[2,4],valid:3,valu:[2,4,6],variou:9,veri:2,version:9,via:[0,4,6,9],vista:[2,4,9],wait:[0,1,4,7],want:[4,9],web:9,well:9,what:[0,1,2,4,8],when:[0,1,2,4,7,8,9],where:[2,4,6,9],whether:[2,4],which:[0,1,2,4,6,8,9],whiteman:9,who:[2,4,9],whose:9,wider:[2,4],wildcard:[2,4],window:[2,3,4,6,9],wish:[1,2],within:[2,4,9],without:[0,9],workgroup:[2,4],wrap:0,write:[2,4,8],write_result:4,written:[2,4],wrong:8,yet:7,you:[0,1,2,3,4,6,8,9],your:[0,1,2,4,6,8,9],yourself:9,zero:[0,1,2,4],zone:[2,4]},titles:["NBNSProtocol Class","NetBIOS class","SMBConnection Class","SMbHandler Class","SMBProtocolFactory Class","SharedDevice Class","SharedFile Class","SMB Exceptions","Extending pysmb For Other Frameworks","Welcome to pysmb’s documentation!"],titleterms:{"class":[0,1,2,3,4,5,6],caveat:[2,4],content:9,credit:9,descript:9,document:9,exampl:[2,3,4],except:7,extend:8,framework:8,indic:9,licens:9,nbnsprotocol:0,netbio:1,note:3,other:8,packag:9,pysmb:[8,9],shareddevic:5,sharedfil:6,smb2:[2,4],smb:7,smbconnect:2,smbhandler:3,smbprotocolfactori:4,support:[2,4],tabl:9,welcom:9}}) \ No newline at end of file +Search.setIndex({envversion:46,filenames:["api/nmb_NBNSProtocol","api/nmb_NetBIOS","api/smb_SMBConnection","api/smb_SMBHandler","api/smb_SMBProtocolFactory","api/smb_SharedDevice","api/smb_SharedFile","api/smb_exceptions","api/smb_security_descriptors","extending","index","upgrading"],objects:{"nmb.NetBIOS":{NetBIOS:[1,0,1,""]},"nmb.NetBIOS.NetBIOS":{"__init__":[1,1,1,""],close:[1,1,1,""],queryIPForName:[1,1,1,""],queryName:[1,1,1,""]},"nmb.NetBIOSProtocol":{NBNSProtocol:[0,0,1,""],NetBIOSTimeout:[0,0,1,""]},"nmb.NetBIOSProtocol.NBNSProtocol":{"__init__":[0,1,1,""],queryIPForName:[0,1,1,""],queryName:[0,1,1,""]},"smb.SMBConnection":{SMBConnection:[2,0,1,""]},"smb.SMBConnection.SMBConnection":{"__init__":[2,1,1,""],SIGN_NEVER:[2,2,1,""],SIGN_WHEN_REQUIRED:[2,2,1,""],SIGN_WHEN_SUPPORTED:[2,2,1,""],close:[2,1,1,""],connect:[2,1,1,""],createDirectory:[2,1,1,""],deleteDirectory:[2,1,1,""],deleteFiles:[2,1,1,""],echo:[2,1,1,""],getAttributes:[2,1,1,""],getSecurity:[2,1,1,""],isUsingSMB2:[2,2,1,""],listPath:[2,1,1,""],listShares:[2,1,1,""],listSnapshots:[2,1,1,""],rename:[2,1,1,""],resetFileAttributes:[2,1,1,""],retrieveFile:[2,1,1,""],retrieveFileFromOffset:[2,1,1,""],storeFile:[2,1,1,""],storeFileFromOffset:[2,1,1,""]},"smb.SMBProtocol":{SMBProtocolFactory:[4,0,1,""]},"smb.SMBProtocol.SMBProtocolFactory":{"__init__":[4,1,1,""],SIGN_NEVER:[4,2,1,""],SIGN_WHEN_REQUIRED:[4,2,1,""],SIGN_WHEN_SUPPORTED:[4,2,1,""],closeConnection:[4,1,1,""],createDirectory:[4,1,1,""],deleteDirectory:[4,1,1,""],deleteFiles:[4,1,1,""],echo:[4,1,1,""],getAttributes:[4,1,1,""],instance:[4,2,1,""],isReady:[4,2,1,""],isUsingSMB2:[4,2,1,""],listPath:[4,1,1,""],listShares:[4,1,1,""],listSnapshots:[4,1,1,""],onAuthFailed:[4,1,1,""],onAuthOK:[4,1,1,""],rename:[4,1,1,""],retrieveFile:[4,1,1,""],retrieveFileFromOffset:[4,1,1,""],storeFile:[4,1,1,""]},"smb.base":{NotConnectedError:[7,0,1,""],NotReadyError:[7,0,1,""],SMBTimeout:[7,0,1,""],SharedDevice:[5,0,1,""],SharedFile:[6,0,1,""]},"smb.base.SharedDevice":{comments:[5,2,1,""],isSpecial:[5,2,1,""],isTemporary:[5,2,1,""],name:[5,2,1,""],type:[5,2,1,""]},"smb.base.SharedFile":{alloc_size:[6,2,1,""],create_time:[6,2,1,""],file_attributes:[6,2,1,""],file_id:[6,2,1,""],file_size:[6,2,1,""],filename:[6,2,1,""],isDirectory:[6,2,1,""],isNormal:[6,2,1,""],isReadOnly:[6,2,1,""],last_access_time:[6,2,1,""],last_attr_change_time:[6,2,1,""],last_write_time:[6,2,1,""],short_name:[6,2,1,""]},"smb.security_descriptors":{ACE:[8,0,1,""],ACL:[8,0,1,""],SID:[8,0,1,""],SecurityDescriptor:[8,0,1,""]},"smb.security_descriptors.ACE":{additional_data:[8,2,1,""],flags:[8,2,1,""],isInheritOnly:[8,2,1,""],mask:[8,2,1,""],sid:[8,2,1,""],type:[8,2,1,""]},"smb.security_descriptors.ACL":{aces:[8,2,1,""],revision:[8,2,1,""]},"smb.security_descriptors.SID":{identifier_authority:[8,2,1,""],revision:[8,2,1,""],subauthorities:[8,2,1,""]},"smb.security_descriptors.SecurityDescriptor":{dacl:[8,2,1,""],flags:[8,2,1,""],group:[8,2,1,""],owner:[8,2,1,""],sacl:[8,2,1,""]},"smb.smb_structs":{OperationFailure:[7,0,1,""],ProtocolError:[7,0,1,""],UnsupportedFeature:[7,0,1,""]},smb:{security_descriptors:[8,3,0,"-"]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"],"3":["py","module","Python module"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute","3":"py:module"},terms:{"3rd":10,"60kbyte":4,"\u5783\u573e\u6587\u4ef6":3,"\u6d4b\u8bd5\u6587\u4ef6\u5939":3,"__init__":[0,1,2,4,9],"abstract":10,"boolean":[0,1,2,4],"byte":[2,4,6],"case":4,"default":[2,4,11],"float":[0,1,4,6],"function":[0,1,2,4,10],"import":[2,3,4,10],"int":[2,4],"long":[2,4,6],"new":[1,2,4,9,11],"public":4,"return":[0,1,2,3,4,5,6,10,11],"short":6,"switch":11,"true":[0,1,2,4,5,6,11],"try":[2,4,9],"while":7,aaa:[0,1],about:[2,4,5,6],abov:9,accept:[4,9],access:[2,4,6,8],access_mask:8,accord:[],accur:4,ace_type_:8,aceflag:8,acetyp:8,acknowledg:4,acl:8,across:[2,4],actual:2,add:[0,4,11],addcallback:[0,4],adderrback:4,addit:8,additional_data:8,address:[0,1,3,9,10,11],admin:5,administr:5,after:[0,1,2,4,10],again:0,against:10,algorithm:[2,4,9,10],all:[2,4,8,10],alloc:6,alloc_s:6,allow:[0,2,4],alon:10,alphanumer:[2,4],alreadi:2,also:[2,4,5],alwai:[8,10],anderson:[0,1],ani:[1,2,4,6,10],api:11,appli:[2,4,8],applic:[0,1,4,6,10,11],application_data:8,appropri:[0,1],arbitari:[2,4],archiv:[2,4,6,11],arg:4,around:0,ascii:[2,4],asn:10,aspx:2,assert:2,associ:8,asynchron:[0,2,9],attempt:2,attr_xxx:6,attribut:[2,4,5,6,11],attribute_data:8,atttempt:2,audit:8,auth:4,authent:[2,4,7,9,10],authenthent:4,author:8,auto:[2,4],automat:[0,1,2,4],avail:[2,4,5,6,10],avoid:4,back:[0,1,2,4],base:[2,4,5,6,7,9,10],batch:4,bbb:[0,1],been:[2,4,7,9,10],befor:[2,4,11],begin:[2,4],between:10,bind:[0,1],bit:[2,4,6,10],bitmask:8,bitwis:[2,4],blob:10,block:[0,1,2,10],both:[2,4],briefli:9,broadcast:[0,1],buffer:9,build_open:3,busi:[2,4],call:[0,1,2,4,6,9],callabl:2,callback:[0,4,9],can:[0,1,2,3,4,5,6,8,9,10,11],cannot:[2,3,4],captur:[2,4],care:9,cc232110:2,ccc:[0,1],chang:[6,11],charact:[2,3,4,6],choic:[2,4],choos:[2,4],cif:[1,2,4,6,7,9,10],clear:2,click:[2,4],client:[2,4,10],client_machine_nam:[2,4],close:[1,2,3,4],closeconnect:4,code:3,com:[2,3],come:10,comm_devic:5,comma:3,command:[2,4],comment:5,commerci:10,commun:[2,4,5,10],compat:[2,4],complet:[2,4,7,10,11],compon:[2,3,4],compress:[2,4,6,11],comput:[2,4],concurr:[2,4],configur:[2,4],conflict:10,conn:2,connect:[2,4,7,9],connecttcp:4,consist:8,constant:[2,4,5,6,8,10],constructor:9,contain:[1,2,4,5,6,10],content:[],context:11,continu:[2,4],contribut:[0,1],control:[2,4,8,11],conveni:[2,4,6,8],copi:[2,4,10],correspond:[6,8],could:[6,10,11],cover:10,creat:[0,1,2,3,4,9],create_tim:6,createdirectori:[2,4],creation:[5,6],credenti:[2,4],credienti:4,current:[2,7],dacl:8,dat:3,data:[2,3,4,8,9,10],data_buf:7,datetim:[2,4],ddd:[0,1],decod:10,def:4,defer:[0,4],defin:[0,1,2,4,6,11],definit:[],delet:[2,3,4,11],delete_matching_fold:[2,11],deletedirectori:[2,4],deletefil:[2,4,11],depend:[8,10],describ:[2,9],descript:[],descriptor:[],detail:[6,9,10],detect:[2,4],determin:[0,1,2,4,6,10],develop:[2,6,10],devic:[5,6],dialect:10,dictionari:8,differ:[2,4,9,10],digest:10,direct:[2,4],directli:[4,6],director:3,directori:[2,3,4,6,11],disabl:[2,4],disconnect:[2,4,7],discret:10,discretionari:8,disk_tre:5,dixon:10,dmitri:10,doe:[2,4,6,10],doesn:8,domain:[2,4,10],done:[0,1],dot:[0,1],download:[4,10],dtyp:8,due:9,each:[1,2,4,5,6],earliest:4,echo:[2,4],edit:[2,4],either:[2,4],element:[2,4],els:[2,4],empti:[0,1,2,3,4,6],enabl:[2,4],encapsul:8,encod:10,encount:[2,6],encrypt:[2,4,6,10,11],end:[2,4],english:[2,4],enterpris:[2,4],entir:4,entri:[2,4,6,8,11],eof:[2,4],errback:[0,4],error:[2,4,9],establish:[2,4],etc:4,excee:4,except:[],exercis:10,exist:[2,3,4],expos:4,facilit:10,factori:4,fail:[2,4,7,9],failur:0,fallback:[2,4],fals:[0,1,2,4],familiar:10,featur:[2,4,7],feeddata:9,fh2:3,field:[6,8],file:[2,3,4,5,6,9,10,11],file_attribut:[2,4,6],file_attribute_norm:2,file_fh:3,file_id:[6,11],file_obj:[2,4],file_s:[4,6],filenam:[2,4,6],fileretriev:4,files:2,filesystem:11,filter:[2,4],find:[2,4,9],finer:11,first:[2,3,4],flag:[0,1,2,4,8],folder:[2,3,4,6,10,11],follow:[2,3,4,5,6,8,10],format:[],forth:5,framework:[],free:[0,1,10],freeli:[2,4],from:[],fscc:6,fulli:3,functionl:4,further:2,gener:9,getattribut:[2,4,11],getsecur:[2,11],given:11,gmt:[2,4],googl:10,group:8,guess:[2,4],handl:[4,9],hash:10,have:[0,2,9,10,11],held:2,help:2,henc:[2,4,10],hidden:[2,4,6,11],hope:[0,1],host:[2,3,4],hostnam:3,http:2,iana:[0,1],identifi:[2,4,8],identifier_author:8,idl:2,ignor:[2,4,6,11],illustr:[2,3,4],immedi:[2,4],implement:[0,1,2,4,8,9,10],impos:[2,4],improv:11,includ:[2,4,9,10,11],incom:0,incompat:11,incomplet:9,index:[2,4,6,10,11],indic:[],individu:4,inform:[2,4,5,6],inherit:8,inherited_object_typ:8,initi:[5,9],insid:[2,4],instal:[2,4,10],instanc:[0,1,2,4,6,8,11],instanti:[0,1,4,6],instiant:[0,1],integ:[0,1,2,4,6,8,10],integr:[5,10],interest:[2,4],intern:[4,9,10],internet:[0,4],interprocess:5,interv:4,invok:[2,4],involv:9,ipc:5,ipv4:1,is_direct_tcp:[2,4],isdirectori:6,isinheritonli:8,isnorm:[6,11],isreadi:4,isreadonli:[6,11],isspeci:5,istemporari:5,isusingsmb2:[2,4],itself:[2,4,8,10],jason:[0,1],join:[],just:[0,4],keep:2,keepal:2,know:[0,1,4],known:[2,4],kwarg:4,last:6,last_access_tim:6,last_attr_change_tim:6,last_write_tim:6,latest:10,learn:[2,4],least:2,leav:[0,1,2,4],length:[6,8],lgpl:10,librari:[2,10],like:[2,3,4,6,11],limit:[2,4,6],linux:10,list:[0,1,2,4,8,10],listen:[0,1],listen_port:[0,1],listenudp:0,listpath:[2,4,6,11],listshar:[2,4],listsnapshot:[2,4],local:[2,3,4],local_fil:3,log:8,look:10,loop:9,loseconnect:4,machin:[0,1,2,3,4,10,11],made:[2,4,10],mai:[2,4],main:10,manag:11,mask:8,match:[0,1,2,4],max_length:[2,4],maximum:[2,4],md4:10,mean:[4,8],meant:2,mechan:[2,4],messag:[2,4,7,10],method:[0,1,2,3,4,6,9,11],microsoft:[2,10],might:[2,4],miketeo:10,mit:10,mode:[0,1],modif:6,modifi:10,modul:[2,4,8,10],more:[2,4,6,9,10],most:[2,4,9,10,11],msdn:2,multipl:[2,4],must:[0,2,3,4],my_nam:[2,4],mypassword:3,myserv:3,myuserid:3,name:[0,1,2,3,4,5,6,10,11],namedtemporaryfil:[2,4],nbn:[1,10],nbname:3,need:[2,3,4,6,9,10],neg:[2,4],net:10,netbio:[],netbiosprotocol:[0,10],netbiossess:10,netbiostimeout:0,network:[0,1,2,4,10],never:[2,4],new_path:[2,4],newer:[2,4],next:2,nmb:[0,1,10,11],non:[2,4],none:[0,1,2,4,5,6,7,8],normal:[2,4,6,11],notat:[0,1,6],notconnectederror:[4,7],note:[],notreadyerror:[4,7],now:[9,11],ntlm:[4,9,10],ntlmssp:10,ntlmv1:[2,4,10],ntlmv2:[2,4,10],number:[0,1,2,4,6,8,11],obj:[2,4],object:[2,3,4,8],object_typ:8,occur:[2,4,7],offset:[2,4],often:9,old:[2,4],old_path:[2,4],older:[],onauthfail:[4,9],onauthok:[4,9],onc:[4,9],onli:[2,3,4,6,8,10,11],onnmbsessionfail:9,open:[2,3,4,10],opensourc:10,oper:[1,2,4,7,9,10,11],operationfailur:[2,4,7],opportun:4,organ:10,origin:[2,4,6],other:[],otherwis:[2,4],out:[2,4],over:[2,4,10],overrid:[4,9],overwritten:[2,4],own:[2,4,9,10],owner:8,packag:[],packet:[0,1,4,9],page:[9,10,11],paramet:[0,1,2,3,4,9,10,11],parent:3,pars:10,part:9,parti:10,pass:[2,3,4,11],password:[2,4],path:[2,3,4],path_file_pattern:[2,4],pathnam:[2,4],pattern:[2,4],perform:[0,1,2,4,6,9],period:4,persist:5,place:10,pleas:[10,11],point:3,port:[0,1,2,4],posit:[2,4],possibl:10,post:[4,9],pre:[],precis:4,present:[7,8],previou:11,princip:8,print:[4,10],print_queu:5,proce:4,proceed:[2,4],process:[3,9],prohibit:6,project:[0,4,10],promis:10,properti:[2,4,6,8,11],protocol:[2,4,7,10],protocolerror:7,provid:[0,1,2,3,4,9,10],pure:10,purpos:10,pyasn1:10,pyde:10,pymsb:4,pysmb:[],python:[2,3,4,10],qualifi:3,queri:[0,1,2,4,10,11],queryipfornam:[0,1,11],querynam:[0,1],queue:4,rais:[0,2,3,4,7],reactor:[0,4],read:[2,3,4,6,9,10,11],readi:[4,7],receiv:[0,1,2,4],recurs:2,refer:[2,4,5,6,10,11],referenc:10,regardless:[2,4],regular:[2,4],reject:[2,4],rel:[2,4],relat:10,releas:[1,2,11],remot:[1,2,3,4,5,6,9,11],remote_nam:[2,4,9],remov:0,renam:[2,4],repli:[0,1,2,4],report:9,repres:[6,8,11],request:[2,3,4],requir:[2,4,7],reserv:5,reset:2,resetfileattribut:2,resolv:3,resourc:[1,2,4,6],respond:2,respons:7,restrict:8,result:[0,2,4],retri:4,retriev:[2,3,4,11],retrievefil:[2,4],retrievefilefactori:4,retrievefilefromoffset:[2,4,11],reus:4,revis:8,rewrit:11,rewritten:11,rfc1001:[2,3,4],right:[2,4],routin:10,rozmanov:10,sacl:8,safe:[2,4],samba:10,sambda:[2,4],same:[2,4],search:[2,4,10,11],sec:4,second:[0,1,4,6],section:10,secur:[],security_descriptor:[2,8],securityblob:10,securitydescriptor:[2,8],see:[2,4,6,8],seek:[2,4],select:[0,1,2,4],self:[4,9],send:[0,1,2,4],separ:[3,10],sequenc:8,sequenti:2,seri:4,server:[2,4,5,6,9,10,11],server_ip:[2,4],server_nam:[2,4],servic:[1,2,3,4,9,10],service_nam:[2,4],session:[9,10],set:[2,4],setup:[0,1,4,9],sha256:10,sha:10,shadow:[2,4],share:[2,3,4,5,6,10],shareddevic:[],sharedfil:[],sharedfold:3,short_nam:6,should:[0,1,2,4,6,8],sid:8,side:10,sign:[2,4],sign_nev:[2,4],sign_opt:[2,4],sign_when_requir:[2,4],sign_when_support:[2,4],simpl:[2,4],simpli:3,simplic:[2,4],sinc:6,singl:[2,4,5,8],site:10,size:[4,6],smb1:[2,4,10],smb2_constant:10,smb2_struct:10,smb:[],smb_constant:[2,4,6,10],smb_ext_file_attr:6,smb_file_attribute_arch:[2,4],smb_file_attribute_directori:[2,4],smb_file_attribute_hidden:[2,4],smb_file_attribute_incl_norm:[2,4],smb_file_attribute_norm:[2,4],smb_file_attribute_readonli:[2,4],smb_file_attribute_system:[2,4],smb_file_attribute_xxx:[2,4],smb_messag:7,smb_struct:[2,4,7,10],smbprotocol:[4,6,10],smbtest:[2,4],smbtimeout:[4,7],snapshot:[2,4],snippet:3,sock_famili:2,socket:[1,2,9],softwar:10,some:[2,4,10],sort:2,sourc:[0,1,2,4,5,6,7,8,10],sp3:10,space:[2,4],spars:[2,4,6,11],special:5,specif:10,specifi:[0,1,2,4,8,11],standard:[0,1,10],start:[0,2,4],statu:6,step:9,stoplisten:0,store:[2,4,6],storefil:[2,4],storefilefromoffset:[2,11],string:[0,1,2,3,4,5,6],structur:8,style:10,sub:[2,4,11],subauthor:8,subclass:[4,9],subsequ:3,success:[2,9],successfulli:[2,4],suitabl:[2,10],support_smb2:[2,4],synchron:[9,10],system:[2,4,6,8,11],take:[9,10],target:[0,1],tcp:[2,4,9],technic:[2,9],tempfil:[2,4],temporari:[2,4,5,6,11],term:10,termin:[2,4],test:[3,6,10],than:[2,4],thei:10,therefor:2,thi:[0,1,2,4,5,6,8,9,10,11],thoma:10,those:4,thousand:4,through:4,time:[2,4,5,6,10],timeout:[0,1,2,4,7,11],todd:10,togeth:10,too:2,total:6,touch:[0,1,4],transfer:[4,10],translat:10,transmit:4,transport:[0,4,10],truncat:[2,11],truste:8,tupl:[2,4],twist:[0,4,10],two:11,txt:[2,3,4],type:[5,8],type_:8,u32:10,udp:[0,1],ultim:[2,4],under:10,underli:[1,2,4,7,10],unicod:[2,3,4,5,6],unless:[0,1],unlock:2,unsign:10,unsupportedfeatur:7,until:[1,2,4],upgrad:[],upload:[2,3,4],upload_fil:3,url:[3,10],urlerror:3,urllib2:[3,10],urllib:3,use_ntlm_v2:[2,4,9],user:[2,4,5,8],userid:[2,4],usernam:[2,4],usual:[2,4,6,9,10],utc:[2,4],utf:3,util:[2,4],valid:[],valu:[2,4,6,8],variabl:8,variou:10,veri:2,version:[],via:[0,4,6,10],vista:[2,4,10],wait:[0,1,4,7],want:[4,10],web:10,well:10,were:11,what:[0,1,2,4,9],when:[0,1,2,4,7,9,10],where:[2,3,4,6,10],whether:[2,4],which:[0,1,2,4,6,8,9,10,11],whiteman:10,who:[2,4,10],whose:10,wider:[2,4],wildcard:[2,4],window:[2,3,4,6,8,10],wise:6,wish:[1,2],within:[2,4,10],without:[0,2,4,10],workgroup:[2,4,11],wrap:0,write:[2,4,9,11],write_result:4,written:[2,4],wrong:9,yet:7,you:[0,1,2,3,4,6,9,10,11],your:[0,1,2,3,4,6,9,10,11],yourself:10,zero:[0,1,2,4],zone:[2,4]},titles:["NBNSProtocol Class","NetBIOS class","SMBConnection Class","SMbHandler Class","SMBProtocolFactory Class","SharedDevice Class","SharedFile Class","SMB Exceptions","Security Descriptors","Extending pysmb For Other Frameworks","Welcome to pysmb’s documentation!","Upgrading from older pysmb versions"],titleterms:{"class":[0,1,2,3,4,5,6],caveat:[2,4],content:10,credit:10,descript:10,descriptor:8,document:10,exampl:[2,3,4],except:7,extend:9,framework:9,from:11,indic:10,licens:10,nbnsprotocol:0,netbio:1,note:3,older:11,other:9,packag:10,pysmb:[9,10,11],secur:8,shareddevic:5,sharedfil:6,smb2:[2,4],smb:7,smbconnect:2,smbhandler:3,smbprotocolfactori:4,support:[2,4],tabl:10,upgrad:11,version:11,welcom:10}}) \ No newline at end of file diff --git a/docs/html/upgrading.html b/docs/html/upgrading.html new file mode 100644 index 0000000..a614019 --- /dev/null +++ b/docs/html/upgrading.html @@ -0,0 +1,206 @@ + + + + + + + + Upgrading from older pysmb versions — pysmb 1.2.1 documentation + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    Upgrading from older pysmb versions

    +

    This page documents the improvements and changes to the API that could be incompatible with previous releases.

    +
    +

    pysmb 1.2.0

    +
      +
    • Add new delete_matching_folders parameter to deleteFiles() method in SMBProtocolFactory and SMBConnection +class to support deletion of sub-folders. If you are passing timeout parameter to the deleteFiles() method +in your application, please switch to using named parameter for timeout.
    • +
    +
    +
    +

    pysmb 1.1.28

    +
      +
    • SharedFile instances returned from the listPath() method now has a new property +file_id attribute which represents the file reference number given by the remote SMB server.
    • +
    +
    +
    +

    pysmb 1.1.26

    +
      +
    • SMBConnection class can now be used as a context manager
    • +
    +
    +
    +

    pysmb 1.1.25

    +
      +
    • SharedFile class has a new property isNormal which will be True if the file is a +‘normal’ file. 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 in SMBProtocolFactory and SMBConnection class will now include +‘normal’ files by default if you do not specify the search parameter.
    • +
    +
    +
    +

    pysmb 1.1.20

    +
      +
    • A new method getSecurity() was added to SMBConnection and SMBProtocolFactory class.
    • +
    +
    +
    +

    pysmb 1.1.15

    +
      +
    • Add new truncate parameter to storeFileFromOffset() in SMBProtocolFactory and SMBConnection +class to support truncation of the file before writing. If you are passing timeout parameter +to the storeFileFromOffset() method in your application, please switch to using named parameter for timeout.
    • +
    +
    +
    +

    pysmb 1.1.11

    +
      +
    • A new method storeFileFromOffset() was added to SMBConnection and SMBProtocolFactory class.
    • +
    +
    +
    +

    pysmb 1.1.10

    +
      +
    • A new method getAttributes() was added to SMBConnection and SMBProtocolFactory class
    • +
    • SharedFile class has a new property isReadOnly to indicate the file is read-only on the remote filesystem.
    • +
    +
    +
    +

    pysmb 1.1.2

    +
      +
    • queryIPForName() method in nmb.NetBIOS and nmb.NBNSProtocol class will now return only the server machine name and ignore workgroup names.
    • +
    +
    +
    +

    pysmb 1.0.3

    +
      +
    • Two new methods were added to NBNSProtocol class: queryIPForName() and NetBIOS.queryIPForName() +to support querying for a machine’s NetBIOS name at the given IP address.
    • +
    • A new method retrieveFileFromOffset() was added to SMBProtocolFactory and SMBConnection +to support finer control of file retrieval operation.
    • +
    +
    +
    +

    pysmb 1.0.0

    +

    pysmb was completely rewritten in version 1.0.0. +If you are upgrading from pysmb 0.x, you most likely have to rewrite your application for the new 1.x API.

    +
    +
    + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/python2/nmb/base.py b/python2/nmb/base.py index 4731dfe..6de0867 100644 --- a/python2/nmb/base.py +++ b/python2/nmb/base.py @@ -77,6 +77,9 @@ self.onNMBSessionOK() elif packet.type == NEGATIVE_SESSION_RESPONSE: self.onNMBSessionFailed() + elif packet.type == SESSION_KEEPALIVE: + # Discard keepalive packets - [RFC1002]: 5.2.2.1 + pass else: self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type) @@ -151,21 +154,25 @@ opcode = (code >> 11) & 0x0F flags = (code >> 4) & 0x7F rcode = code & 0x0F - numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0] - if numnames > 0: - ret = [ ] - offset = self.HEADER_STRUCT_SIZE + 45 + try: + numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0] - for i in range(0, numnames): - mynme = data[offset:offset + 15] - mynme = mynme.strip() - ret.append(( mynme, ord(data[offset+15]) )) - offset += 18 + if numnames > 0: + ret = [ ] + offset = self.HEADER_STRUCT_SIZE + 45 - return trn_id, ret - else: - return trn_id, None + for i in range(0, numnames): + mynme = data[offset:offset + 15] + mynme = mynme.strip() + ret.append(( mynme, ord(data[offset+15]) )) + offset += 18 + + return trn_id, ret + except IndexError: + pass + + return trn_id, None # # Contributed by Jason Anderson diff --git a/python2/smb/SMBConnection.py b/python2/smb/SMBConnection.py index 77b1760..cae3ab9 100644 --- a/python2/smb/SMBConnection.py +++ b/python2/smb/SMBConnection.py @@ -21,6 +21,7 @@ Create a new SMBConnection instance. *username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server. + *password* can be a string or a callable returning a string. File operations can only be proceeded after the connection has been authenticated successfully. Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication. @@ -29,7 +30,7 @@ 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 ``\/:*?";|+`` + 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. @@ -72,6 +73,15 @@ total_sent = total_sent + sent # + # Support for "with" context + # + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + # # Misc Properties # @@ -153,15 +163,23 @@ return results def listPath(self, service_name, path, - search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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` instances. """ @@ -245,6 +263,37 @@ self.is_busy = True try: self._getAttributes(service_name, path, cb, eb, timeout) + while self.is_busy: + self._pollForNetBIOSPacket(timeout) + finally: + self.is_busy = False + + return results[0] + + def getSecurity(self, service_name, path, timeout = 30): + """ + Retrieve the security descriptor of the file at *path* on the *service_name*. + + :param string/unicode service_name: the name of the shared folder for the *path* + :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure` will be raised. + :return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file. + """ + if not self.sock: + raise NotConnectedError('Not connected to server') + + results = [ ] + + def cb(info): + self.is_busy = False + results.append(info) + + def eb(failure): + self.is_busy = False + raise failure + + self.is_busy = True + try: + self._getSecurity(service_name, path, cb, eb, timeout) while self.is_busy: self._pollForNetBIOSPacket(timeout) finally: @@ -350,9 +399,11 @@ return results[0] - def deleteFiles(self, service_name, path_file_pattern, timeout = 30): + def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30): """ Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request. + + If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively. :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. @@ -372,23 +423,27 @@ 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 - - def resetFileAttributes(self, service_name, path_file_pattern, timeout = 30): + self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout) + while self.is_busy: + self._pollForNetBIOSPacket(timeout) + finally: + self.is_busy = False + + def resetFileAttributes(self, service_name, path_file_pattern, file_attributes = ATTR_NORMAL, timeout = 30): """ Reset file attributes of one or more regular files or folders. It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request. This function is very helpful when deleting files/folders that are read-only. - 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) - + By default, it sets the ATTR_NORMAL flag, therefore clearing all other flags. + (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information) + + Note: this function is currently only implemented for SMB2! + :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. Wildcards may be used in the filename component of the path. If your path/filename contains non-English characters, you must pass in an unicode string. + :param int file_attributes: The desired file attributes to set. Defaults to `ATTR_NORMAL`. :return: None """ if not self.sock: @@ -403,7 +458,7 @@ self.is_busy = True try: - self._resetFileAttributes(service_name, path_file_pattern, cb, eb, timeout = timeout) + self._resetFileAttributes(service_name, path_file_pattern, cb, eb, file_attributes, timeout = timeout) while self.is_busy: self._pollForNetBIOSPacket(timeout) finally: @@ -495,7 +550,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :return: The *data* parameter """ if not self.sock: diff --git a/python2/smb/SMBHandler.py b/python2/smb/SMBHandler.py index 137e943..4fcc74f 100644 --- a/python2/smb/SMBHandler.py +++ b/python2/smb/SMBHandler.py @@ -44,12 +44,15 @@ passwd = passwd or '' myname = MACHINE_NAME or self.generateClientMachineName() - n = NetBIOS() - names = n.queryIPForName(host) - if names: - server_name = names[0] - else: - raise urllib2.URLError('SMB error: Hostname does not reply back with its machine name') + server_name,host = host.split(',') if ',' in host else [None,host] + + if server_name is None: + n = NetBIOS() + names = n.queryIPForName(host) + if names: + server_name = names[0] + else: + raise urllib2.URLError('SMB error: Hostname does not reply back with its machine name') path, attrs = splitattr(req.get_selector()) if path.startswith('/'): diff --git a/python2/smb/SMBProtocol.py b/python2/smb/SMBProtocol.py index 2af2747..1238d63 100644 --- a/python2/smb/SMBProtocol.py +++ b/python2/smb/SMBProtocol.py @@ -177,15 +177,23 @@ return d def listPath(self, service_name, path, - search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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` instances. @@ -368,7 +376,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter. """ diff --git a/python2/smb/base.py b/python2/smb/base.py index c1544c8..32a1ac7 100644 --- a/python2/smb/base.py +++ b/python2/smb/base.py @@ -5,6 +5,7 @@ from smb2_constants import * from smb_structs import * from smb2_structs import * +from .security_descriptors import SecurityDescriptor from nmb.base import NMBSession from utils import convertFILETIMEtoEpoch import ntlm, securityblob @@ -59,13 +60,14 @@ 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._password = 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.async_requests = { } #: AsyncID mapped to _PendingRequest instance self.pending_requests = { } #: MID mapped to _PendingRequest instance self.connected_trees = { } #: Share name mapped to TID self.next_rpc_call_id = 1 #: Next RPC callID value. Not used directly in SMB message. Usually encapsulated in sub-commands under SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2 messages @@ -104,6 +106,10 @@ (self.use_ntlm_v2 and 'v2') or 'v1', (SUPPORT_EXTENDED_SECURITY and 'with') or 'without') + @property + def password(self): + password = self._password() if callable(self._password) else self._password + return _convert_to_unicode(password) # # NMBSession Methods @@ -173,6 +179,7 @@ self._listShares = self._listShares_SMB1 self._listPath = self._listPath_SMB1 self._listSnapshots = self._listSnapshots_SMB1 + self._getSecurity = self._getSecurity_SMB1 self._getAttributes = self._getAttributes_SMB1 self._retrieveFile = self._retrieveFile_SMB1 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1 @@ -196,6 +203,7 @@ self._listPath = self._listPath_SMB2 self._listSnapshots = self._listSnapshots_SMB2 self._getAttributes = self._getAttributes_SMB2 + self._getSecurity = self._getSecurity_SMB2 self._retrieveFile = self._retrieveFile_SMB2 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB2 self._storeFile = self._storeFile_SMB2 @@ -219,7 +227,7 @@ 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: + if smb_message.command != SMB2_COM_NEGOTIATE: smb_message.session_id = self.session_id if self.is_signing_active: @@ -256,6 +264,19 @@ if result == securityblob.RESULT_ACCEPT_COMPLETED: self.has_authenticated = True self.log.info('Authentication (on SMB2) successful!') + + # [MS-SMB2]: 3.2.5.3.1 + # If the security subsystem indicates that the session was established by an anonymous user, + # Session.SigningRequired MUST be set to FALSE. + # If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the + # SMB2 SESSION_SETUP Response and if Session.SigningRequired is TRUE, this indicates a SESSION_SETUP + # failure and the connection MUST be terminated. If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags + # field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired + # MUST be set to FALSE. + if message.payload.isGuestSession or message.payload.isAnonymousSession: + self.is_signing_active = False + self.log.info('Signing disabled because session is guest/anonymous') + self.onAuthOK() else: raise ProtocolError('SMB2_COM_SESSION_SETUP status is 0 but security blob negResult value is %d' % result, message.raw_data, message) @@ -269,18 +290,58 @@ 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 + elif (message.status == 0xc000006d # STATUS_LOGON_FAILURE + or message.status == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status == 0xc000006a):# STATUS_WRONG_PASSWORD self.has_authenticated = False self.log.info('Authentication (on SMB2) failed. Please check username and password.') self.onAuthFailed() + elif (message.status == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Not allowed.') + self.onAuthFailed() + elif message.status == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Workstation not trusted.') + 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 + if message.isAsync: + if message.status == 0x00000103: # STATUS_PENDING + req = self.pending_requests.pop(message.mid, None) + if req: + self.async_requests[message.async_id] = req + else: # All other status including SUCCESS + req = self.async_requests.pop(message.async_id, None) + if req: + req.callback(message, **req.kwargs) + return True + else: + req = self.pending_requests.pop(message.mid, None) + if req: + req.callback(message, **req.kwargs) + return True def _updateServerInfo_SMB2(self, payload): @@ -315,12 +376,13 @@ 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) + ntlm_data, session_signing_key = ntlm.generateAuthenticateMessage(server_flags, + nt_challenge_response, + lm_challenge_response, + session_key, + self.username, + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -340,7 +402,10 @@ 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] + self.signing_session_key = session_signing_key + if self.log.isEnabledFor(logging.DEBUG): + self.log.info("SMB signing key is %s", binascii.hexlify(self.signing_session_key)) + if self.capabilities & CAP_EXTENDED_SECURITY: self.signing_challenge_response = None else: @@ -369,7 +434,7 @@ m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback, tid = tid) messages_history.append(m) def connectSrvSvcCB(create_message, **kwargs): @@ -391,9 +456,9 @@ 01 00 00 00 """.replace(' ', '').replace('\n', '')) m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0)) - m.tid = create_message.tid + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) messages_history.append(m) else: errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history)) @@ -402,12 +467,12 @@ 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 + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint') def rpcReadCB(read_message, **kwargs): messages_history.append(read_message) @@ -435,12 +500,12 @@ 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 + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint') def listShareResultsCB(result_message, **kwargs): messages_history.append(result_message) @@ -449,13 +514,11 @@ 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], data_bytes) + else: + decodeResults(kwargs['tid'], kwargs['fid'], data_bytes) + else: + closeFid(kwargs['tid'], kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) def decodeResults(tid, fid, data_bytes): @@ -494,20 +557,19 @@ 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) + tid = tid, 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + closeFid(kwargs['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): @@ -572,39 +634,44 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: - sendQuery(create_message.tid, create_message.payload.fid, '') + sendQuery(kwargs['tid'], create_message.payload.fid, '') + elif create_message.status == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to list %s on %s: Path not found' % ( path, service_name ), messages_history)) else: errback(OperationFailure('Failed to list %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) def sendQuery(tid, fid, data_buf): m = SMB2Message(SMB2QueryDirectoryRequest(fid, pattern, - info_class = 0x03, # FileBothDirectoryInformation + info_class = 0x25, # FileIdBothDirectoryInformation 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = tid, 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) + sendQuery(kwargs['tid'], kwargs['fid'], data_buf) + elif query_message.status == 0xC000000FL: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing + closeFid(kwargs['tid'], kwargs['fid'], results = results) elif query_message.status == 0x80000006L: # STATUS_NO_MORE_FILES - closeFid(query_message.tid, kwargs['fid'], results = results) - else: - closeFid(query_message.tid, kwargs['fid'], error = query_message.status) + closeFid(kwargs['tid'], kwargs['fid'], results = results) + else: + closeFid(kwargs['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 = ' 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)) + short_name = short_name[:short_name_length].decode('UTF-16LE') + + accept_result = False + if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc + accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL) + else: + accept_result = (file_attributes & search) > 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename, file_id)) if next_offset: offset += next_offset @@ -645,7 +719,11 @@ 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 kwargs['error'] == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) if not self.connected_trees.has_key(service_name): def connectCB(connect_message, **kwargs): @@ -695,17 +773,18 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: p = create_message.payload + filename = self._extractLastPathComponent(unicode(path)) 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) + filename, filename) + closeFid(kwargs['tid'], p.fid, info = info) else: errback(OperationFailure('Failed to get attributes for %s on %s: Unable to open remote file object' % ( path, service_name ), messages_history)) @@ -730,6 +809,87 @@ 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 _getSecurity_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 = [ ] + results = [ ] + + def sendCreate(tid): + 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 | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + oplock = SMB2_OPLOCK_LEVEL_NONE, + impersonation = SEC_IMPERSONATE, + create_options = 0, + create_disp = FILE_OPEN)) + 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 = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + info_type = SMB2_INFO_SECURITY, + file_info_class = 0, # [MS-SMB2] 2.2.37, 3.2.4.12 + input_buf = '', + output_buf_len = self.max_transact_size)) + m.tid = kwargs['tid'] + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) + messages_history.append(m) + else: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to open file or directory' % ( path, service_name ), messages_history)) + + def queryCB(query_message, **kwargs): + messages_history.append(query_message) + if query_message.status == 0: + security = SecurityDescriptor.from_bytes(query_message.payload.data) + closeFid(kwargs['tid'], kwargs['fid'], result = security) + else: + closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status) + + def closeFid(tid, fid, result = 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, result = result, error = error) + messages_history.append(m) + + def closeCB(close_message, **kwargs): + if kwargs['result'] is not None: + callback(kwargs['result']) + elif kwargs['error'] is not None: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) + + 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 the security descriptor 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) @@ -766,7 +926,7 @@ 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, + share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, oplock = SMB2_OPLOCK_LEVEL_NONE, impersonation = SEC_IMPERSONATE, create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE, @@ -787,13 +947,15 @@ file_info_class = 0x16, # FileStreamInformation [MS-FSCC] 2.4 input_buf = '', output_buf_len = 4096)) - m.tid = create_message.tid + m.tid = kwargs['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) + tid = kwargs['tid'], + 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)) + errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history)) def infoCB(info_message, **kwargs): messages_history.append(info_message) @@ -808,9 +970,9 @@ 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)) + sendRead(kwargs['tid'], kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes']) + else: + errback(OperationFailure('Failed to retrieve %s on %s: Unable to retrieve information on file' % ( path, service_name ), messages_history)) def sendRead(tid, fid, offset, remaining_len, read_len, file_attributes): read_count = min(self.max_read_size, remaining_len) @@ -818,7 +980,7 @@ m.tid = tid self._sendSMBMessage(m) self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, - fid = fid, offset = offset, + tid = tid, fid = fid, offset = offset, remaining_len = remaining_len, read_len = read_len, file_attributes = file_attributes) @@ -832,12 +994,12 @@ 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 )) + sendRead(kwargs['tid'], kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes']) + else: + closeFid(kwargs['tid'], kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len )) else: messages_history.append(read_message) - closeFid(read_message.tid, kwargs['fid'], error = read_message.status) + closeFid(kwargs['tid'], kwargs['fid'], error = read_message.status) def closeFid(tid, fid, ret = None, error = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -913,10 +1075,6 @@ messages_history.append(create_message) if create_message.status == 0: sendWrite(create_message.tid, create_message.payload.fid, starting_offset) - elif create_message.status == 0x0103: # STATUS_PENDING - self.pending_requests[create_message.mid] = _PendingRequest(create_message.mid, expiry_time, - createCB, errback, - tid=kwargs['tid']) else: errback(OperationFailure('Failed to store %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -928,17 +1086,17 @@ 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, tid = tid, fid = fid, offset = offset+data_len) else: closeFid(tid, fid, offset = offset) def writeCB(write_message, **kwargs): # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history. if write_message.status == 0: - sendWrite(write_message.tid, kwargs['fid'], kwargs['offset']) + sendWrite(kwargs['tid'], kwargs['fid'], kwargs['offset']) else: messages_history.append(write_message) - closeFid(write_message.tid, kwargs['fid']) + closeFid(kwargs['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): @@ -971,16 +1129,99 @@ sendCreate(self.connected_trees[service_name]) - def _deleteFiles_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB2(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') expiry_time = time.time() + timeout + pattern = None path = path_file_pattern.replace('/', '\\') if path.startswith('\\'): path = path[1:] if path.endswith('\\'): path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB2__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB2(service_name, p, deleteCB, errback, timeout) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + if 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 + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB2__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB2(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB2(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + def _deleteFiles_SMB2__del(self, service_name, tid, path, callback, errback, timeout = 30): messages_history = [ ] def sendCreate(tid): @@ -1011,10 +1252,10 @@ messages_history.append(open_message) if open_message.status == 0: sendDelete(open_message.tid, open_message.payload.fid) - elif open_message.status == 0x0103: # STATUS_PENDING - self.pending_requests[open_message.mid] = _PendingRequest(open_message.mid, expiry_time, - createCB, errback, - tid=kwargs['tid']) + elif open_message.status == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_NOT_FOUND + callback(path) + elif open_message.status == 0xC00000BAL: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) else: errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -1024,22 +1265,18 @@ 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) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4.11 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1050,27 +1287,13 @@ def closeCB(close_message, **kwargs): if kwargs['status'] == 0: - callback(path_file_pattern) + 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 _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): + sendCreate(tid) + + def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, file_attributes = ATTR_NORMAL, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') @@ -1109,7 +1332,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendReset(open_message.tid, open_message.payload.fid) + sendReset(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -1118,25 +1341,19 @@ 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) + data = struct.pack('qqqqii',0,0,0,0,file_attributes,0))) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4, [MS-FSCC]: 2.4.7, [MS-FSCC]: 2.6 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = reset_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1199,13 +1416,13 @@ 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) + 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: - closeFid(create_message.tid, create_message.payload.fid) + closeFid(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history)) @@ -1273,7 +1490,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) + sendDelete(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) @@ -1285,15 +1502,15 @@ data = '\x01')) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1369,7 +1586,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendRename(create_message.tid, create_message.payload.fid) + sendRename(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to rename %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1382,15 +1599,15 @@ data = data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = rename_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1457,7 +1674,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendEnumSnapshots(create_message.tid, create_message.payload.fid) + sendEnumSnapshots(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1598,9 +1815,38 @@ 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 + elif (message.status.internal_value == 0xc000006d # STATUS_LOGON_FAILURE + or message.status.internal_value == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status.internal_value == 0xc000006a): # STATUS_WRONG_PASSWORD self.has_authenticated = False - self.log.info('Authentication (with extended security) failed. Please check username and password. You may need to enable/disable NTLMv2 authentication.') + self.log.info('Authentication (with extended security) failed. Please check username and password.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status.internal_value == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status.internal_value == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status.internal_value == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Not allowed.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Workstation not trusted.') self.onAuthFailed() else: raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status.internal_value, @@ -1662,12 +1908,13 @@ 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) + ntlm_data, session_signing_key = ntlm.generateAuthenticateMessage(server_flags, + nt_challenge_response, + lm_challenge_response, + session_key, + self.username, + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -1687,7 +1934,7 @@ if self.is_signing_active: self.log.info("SMB signing activated. All SMB messages will be signed.") - self.signing_session_key = session_key + self.signing_session_key = session_signing_key if self.capabilities & CAP_EXTENDED_SECURITY: self.signing_challenge_response = None else: @@ -1861,13 +2108,12 @@ 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]) + sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) else: closeFid(read_message.tid, kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) @@ -1906,15 +2152,15 @@ setup_bytes = struct.pack(' 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename)) if next_offset: offset += next_offset @@ -1992,11 +2245,15 @@ 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): + sendFindNext(find_message.tid, sid, 0, results[-1].filename, kwargs.get('support_dfs', False)) + else: + if find_message.status.internal_value == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history)) + + def sendFindNext(tid, sid, resume_key, resume_file, support_dfs=False): setup_bytes = struct.pack(' 0: @@ -2322,11 +2580,100 @@ else: sendOpen(self.connected_trees[service_name]) - def _deleteFiles_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB1(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') + expiry_time = time.time() + timeout + pattern = None path = path_file_pattern.replace('/', '\\') + if path.startswith('\\'): + path = path[1:] + if path.endswith('\\'): + path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB1__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB1(service_name, p, deleteCB, errback, timeout = 30) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + 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 files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB1__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB1(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB1(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + + def _deleteFiles_SMB1__del(self, service_name, tid, path, callback, errback, timeout = 30): messages_history = [ ] def sendDelete(tid): @@ -2340,9 +2687,79 @@ def deleteCB(delete_message, **kwargs): messages_history.append(delete_message) if not delete_message.status.hasError: + callback(path) + elif delete_message.status.internal_value == 0xC000000FL: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing callback(path_file_pattern) - else: - errback(OperationFailure('Failed to store %s on %s: Delete failed' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC00000BAL: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC0000034L: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to delete %s on %s: Path not found' % ( path, service_name ), messages_history)) + else: + errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) + + sendDelete(tid) + + def _resetFileAttributes_SMB1(self, service_name, path_file_pattern, callback, errback, file_attributes=ATTR_NORMAL, timeout = 30): + raise NotReadyError('resetFileAttributes is not yet implemented for SMB1') + + def _createDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30): + 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): @@ -2351,84 +2768,7 @@ 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)) + errback(OperationFailure('Failed to delete 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) @@ -2563,6 +2903,9 @@ def _echo_SMB1(self, data, callback, errback, timeout = 30): messages_history = [ ] + if not isinstance(data, type(b'')): + raise TypeError('Echo data must be %s not %s' % (type(b'').__name__, type(data).__name__)) + def echoCB(echo_message, **kwargs): messages_history.append(echo_message) if not echo_message.status.hasError: @@ -2575,10 +2918,18 @@ self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback) messages_history.append(m) + def _extractLastPathComponent(self, path): + return path.replace('\\', '/').split('/')[-1] + class SharedDevice: """ Contains information about a single shared device on the remote server. + + The following attributes are available: + + * name : An unicode string containing the name of the shared device + * comments : An unicode string containing the user description of the shared device """ # The following constants are taken from [MS-SRVS]: 2.2.2.4 @@ -2633,19 +2984,33 @@ 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). + one of these prohibited characters: "\\/[]:+|<>=;?,* (see [MS-CIFS]: 2.2.1.1.1 for more details). + + The following attributes are available: + + * create_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server + * last_access_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server + * last_write_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server + * last_attr_change_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server + * file_size : File size in number of bytes + * alloc_size : Total number of bytes allocated to store this file + * file_attributes : A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. + * short_name : Unicode string containing the short name of this file (usually in 8.3 notation) + * filename : Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + * file_id : Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 """ - def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename): + def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename, file_id=None): self.create_time = create_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server self.last_access_time = last_access_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server self.last_write_time = last_write_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server self.last_attr_change_time = last_attr_change_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server self.file_size = file_size #: File size in number of bytes self.alloc_size = alloc_size #: Total number of bytes allocated to store this file - self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3 + self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. self.short_name = short_name #: Unicode string containing the short name of this file (usually in 8.3 notation) self.filename = filename #: Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + self.file_id = file_id #: Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 @property def isDirectory(self): @@ -2656,6 +3021,16 @@ def isReadOnly(self): """A convenient property to return True if this file resource is read-only on the remote server""" return bool(self.file_attributes & ATTR_READONLY) + + @property + def isNormal(self): + """ + A convenient property to return True if this is a normal file. + + Note that pysmb defines a normal file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + """ + return (self.file_attributes==ATTR_NORMAL) or ((self.file_attributes & 0xff)==0) def __unicode__(self): return u'Shared file: %s (FileSize:%d bytes, isDirectory:%s)' % ( self.filename, self.file_size, self.isDirectory ) diff --git a/python2/smb/ntlm.py b/python2/smb/ntlm.py index ae6fc9e..e9acc36 100644 --- a/python2/smb/ntlm.py +++ b/python2/smb/ntlm.py @@ -1,5 +1,6 @@ -import types, hmac, binascii, struct, random +import types, hmac, binascii, struct, random, string +from .utils.rc4 import RC4_encrypt from utils.pyDes import des try: @@ -58,14 +59,14 @@ NTLM_FLAGS = NTLM_NegotiateUnicode | \ NTLM_RequestTarget | \ + NTLM_NegotiateSign | \ NTLM_NegotiateNTLM | \ NTLM_NegotiateAlwaysSign | \ NTLM_NegotiateExtendedSecurity | \ NTLM_NegotiateTargetInfo | \ NTLM_NegotiateVersion | \ NTLM_Negotiate128 | \ - NTLM_NegotiateKeyExchange | \ - NTLM_Negotiate56 + NTLM_NegotiateKeyExchange def generateNegotiateMessage(): """ @@ -81,7 +82,7 @@ return s -def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'): +def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, request_session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'): """ References: =========== @@ -89,6 +90,13 @@ """ FORMAT = '<8sIHHIHHIHHIHHIHHIHHII' FORMAT_SIZE = struct.calcsize(FORMAT) + + # [MS-NLMP]: 3.1.5.1.2 + # http://grutz.jingojango.net/exploits/davenport-ntlm.html + session_key = session_signing_key = request_session_key + if challenge_flags & NTLM_NegotiateKeyExchange: + session_signing_key = "".join([ random.choice(string.digits+string.ascii_letters) for _ in range(16) ]).encode('ascii') + session_key = RC4_encrypt(request_session_key, session_signing_key) lm_response_length = len(lm_response) lm_response_offset = FORMAT_SIZE @@ -125,7 +133,7 @@ session_key_length, session_key_length, session_key_offset, auth_flags) - return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key + return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key, session_signing_key def decodeChallengeMessage(ntlm_data): diff --git a/python2/smb/security_descriptors.py b/python2/smb/security_descriptors.py new file mode 100644 index 0000000..9e6ebe1 --- /dev/null +++ b/python2/smb/security_descriptors.py @@ -0,0 +1,367 @@ +""" +This module implements security descriptors, and the partial structures +used in them, as specified in [MS-DTYP]. +""" + +import struct + + +# Security descriptor control flags +# [MS-DTYP]: 2.4.6 +SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001 +SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002 +SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004 +SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008 +SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010 +SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020 +SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040 +SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080 +SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100 +SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200 +SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400 +SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800 +SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000 +SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000 +SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000 +SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000 + +# ACE types +# [MS-DTYP]: 2.4.4.1 +ACE_TYPE_ACCESS_ALLOWED = 0x00 +ACE_TYPE_ACCESS_DENIED = 0x01 +ACE_TYPE_SYSTEM_AUDIT = 0x02 +ACE_TYPE_SYSTEM_ALARM = 0x03 +ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04 +ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05 +ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06 +ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07 +ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08 +ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09 +ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A +ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B +ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C +ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D +ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E +ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F +ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10 +ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11 +ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12 +ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13 + +# ACE flags +# [MS-DTYP]: 2.4.4.1 +ACE_FLAG_OBJECT_INHERIT = 0x01 +ACE_FLAG_CONTAINER_INHERIT = 0x02 +ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04 +ACE_FLAG_INHERIT_ONLY = 0x08 +ACE_FLAG_INHERITED = 0x10 +ACE_FLAG_SUCCESSFUL_ACCESS = 0x40 +ACE_FLAG_FAILED_ACCESS = 0x80 + +# Pre-defined well-known SIDs +# [MS-DTYP]: 2.4.2.4 +SID_NULL = "S-1-0-0" +SID_EVERYONE = "S-1-1-0" +SID_LOCAL = "S-1-2-0" +SID_CONSOLE_LOGON = "S-1-2-1" +SID_CREATOR_OWNER = "S-1-3-0" +SID_CREATOR_GROUP = "S-1-3-1" +SID_OWNER_SERVER = "S-1-3-2" +SID_GROUP_SERVER = "S-1-3-3" +SID_OWNER_RIGHTS = "S-1-3-4" +SID_NT_AUTHORITY = "S-1-5" +SID_DIALUP = "S-1-5-1" +SID_NETWORK = "S-1-5-2" +SID_BATCH = "S-1-5-3" +SID_INTERACTIVE = "S-1-5-4" +SID_SERVICE = "S-1-5-6" +SID_ANONYMOUS = "S-1-5-7" +SID_PROXY = "S-1-5-8" +SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9" +SID_PRINCIPAL_SELF = "S-1-5-10" +SID_AUTHENTICATED_USERS = "S-1-5-11" +SID_RESTRICTED_CODE = "S-1-5-12" +SID_TERMINAL_SERVER_USER = "S-1-5-13" +SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14" +SID_THIS_ORGANIZATION = "S-1-5-15" +SID_IUSR = "S-1-5-17" +SID_LOCAL_SYSTEM = "S-1-5-18" +SID_LOCAL_SERVICE = "S-1-5-19" +SID_NETWORK_SERVICE = "S-1-5-20" +SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496" +SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497" +SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544" +SID_BUILTIN_USERS = "S-1-5-32-545" +SID_BUILTIN_GUESTS = "S-1-5-32-546" +SID_POWER_USERS = "S-1-5-32-547" +SID_ACCOUNT_OPERATORS = "S-1-5-32-548" +SID_SERVER_OPERATORS = "S-1-5-32-549" +SID_PRINTER_OPERATORS = "S-1-5-32-550" +SID_BACKUP_OPERATORS = "S-1-5-32-551" +SID_REPLICATOR = "S-1-5-32-552" +SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554" +SID_REMOTE_DESKTOP = "S-1-5-32-555" +SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556" +SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557" +SID_PERFMON_USERS = "S-1-5-32-558" +SID_PERFLOG_USERS = "S-1-5-32-559" +SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560" +SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561" +SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562" +SID_IIS_IUSRS = "S-1-5-32-568" +SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569" +SID_EVENT_LOG_READERS = "S-1-5-32-573" +SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574" +SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575" +SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576" +SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577" +SID_HYPER_V_ADMINS = "S-1-5-32-578" +SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579" +SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580" +SID_WRITE_RESTRICTED_CODE = "S-1-5-33" +SID_NTLM_AUTHENTICATION = "S-1-5-64-10" +SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14" +SID_DIGEST_AUTHENTICATION = "S-1-5-64-21" +SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1" +SID_NT_SERVICE = "S-1-5-80" +SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0" +SID_LOCAL_ACCOUNT = "S-1-5-113" +SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114" +SID_OTHER_ORGANIZATION = "S-1-5-1000" +SID_ALL_APP_PACKAGES = "S-1-15-2-1" +SID_ML_UNTRUSTED = "S-1-16-0" +SID_ML_LOW = "S-1-16-4096" +SID_ML_MEDIUM = "S-1-16-8192" +SID_ML_MEDIUM_PLUS = "S-1-16-8448" +SID_ML_HIGH = "S-1-16-12288" +SID_ML_SYSTEM = "S-1-16-16384" +SID_ML_PROTECTED_PROCESS = "S-1-16-20480" +SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1" +SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2" +SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3" +SID_KEY_TRUST_IDENTITY = "S-1-18-4" +SID_KEY_PROPERTY_MFA = "S-1-18-5" +SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6" + + +class SID(object): + """ + A Windows security identifier. Represents a single principal, such a + user or a group, as a sequence of numbers consisting of the revision, + identifier authority, and a variable-length list of subauthorities. + + See [MS-DTYP]: 2.4.2 + """ + def __init__(self, revision, identifier_authority, subauthorities): + #: Revision, should always be 1. + self.revision = revision + #: An integer representing the identifier authority. + self.identifier_authority = identifier_authority + #: A list of integers representing all subauthorities. + self.subauthorities = subauthorities + + def __str__(self): + """ + String representation, as specified in [MS-DTYP]: 2.4.2.1 + """ + if self.identifier_authority >= 2**32: + id_auth = '%#x' % (self.identifier_authority,) + else: + id_auth = self.identifier_authority + auths = [self.revision, id_auth] + self.subauthorities + return 'S-' + '-'.join(str(subauth) for subauth in auths) + + def __repr__(self): + return 'SID(%r)' % (str(self),) + + @classmethod + def from_bytes(cls, data, return_tail=False): + revision, subauth_count = struct.unpack('Q', '\x00\x00' + data[2:8])[0] + subauth_data = data[8:] + subauthorities = [struct.unpack('= size + + body = data[header_size:size] + additional_data = {} + + # In all ACE types, the mask immediately follows the header. + mask = struct.unpack('= size + + for i in range(count): + ace_size = struct.unpack(' 0 # SMB2_SESSION_FLAG_IS_GUEST + + @property + def isAnonymousSession(self): + return (self.session_flags & 0x0002) > 0 # SMB2_SESSION_FLAG_IS_NULL + def decode(self, message): assert message.command == SMB2_COM_SESSION_SETUP @@ -362,6 +370,7 @@ def prepare(self, message): buf = self.filename.encode('UTF-16LE') + filename_len = len(buf) if self.create_context_data: n = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf) if n % 8 != 0: @@ -389,7 +398,7 @@ self.create_disp, self.create_options, SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # NameOffset - len(self.filename)*2, # NameLength in bytes + filename_len, # Length of encoded filename in bytes create_context_offset, # CreateContextOffset len(self.create_context_data) # CreateContextLength ) + buf diff --git a/python2/smb/smb_constants.py b/python2/smb/smb_constants.py index 79a7514..9947680 100644 --- a/python2/smb/smb_constants.py +++ b/python2/smb/smb_constants.py @@ -115,6 +115,7 @@ FILE_READ_EA = 0x08 FILE_WRITE_EA = 0x10 FILE_EXECUTE = 0x20 +FILE_DELETE_CHILD = 0x40 FILE_READ_ATTRIBUTES = 0x80 FILE_WRITE_ATTRIBUTES = 0x0100 DELETE = 0x010000 @@ -225,9 +226,13 @@ SMB_FILE_ATTRIBUTE_READONLY = 0x01 SMB_FILE_ATTRIBUTE_HIDDEN = 0x02 SMB_FILE_ATTRIBUTE_SYSTEM = 0x04 -SMB_FILE_ATTRIBUTE_VOLUME = 0x08 +SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10 SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20 +# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files for +# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs. +SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000 +# Do not use the following values for listPath() operations as they are not supported for SMB2 SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100 SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200 SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400 @@ -237,3 +242,16 @@ # Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001 SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002 + +# Bitmask for security information fields, specified as +# AdditionalInformation in SMB2 +# [MS-SMB]: 2.2.7.4 +# [MS-SMB2]: 2.2.37 +OWNER_SECURITY_INFORMATION = 0x00000001 +GROUP_SECURITY_INFORMATION = 0x00000002 +DACL_SECURITY_INFORMATION = 0x00000004 +SACL_SECURITY_INFORMATION = 0x00000008 +LABEL_SECURITY_INFORMATION = 0x00000010 +ATTRIBUTE_SECURITY_INFORMATION = 0x00000020 +SCOPE_SECURITY_INFORMATION = 0x00000040 +BACKUP_SECURITY_INFORMATION = 0x00010000 diff --git a/python2/smb/smb_structs.py b/python2/smb/smb_structs.py index 8fab1d6..65ecac0 100644 --- a/python2/smb/smb_structs.py +++ b/python2/smb/smb_structs.py @@ -1280,7 +1280,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 diff --git a/python2/smb/utils/rc4.py b/python2/smb/utils/rc4.py new file mode 100644 index 0000000..490a2f0 --- /dev/null +++ b/python2/smb/utils/rc4.py @@ -0,0 +1,22 @@ + +def RC4_encrypt(key, data): + S = list(range(256)) + j = 0 + + key_len = len(key) + for i in list(range(256)): + j = (j + S[i] + ord(key[i % key_len])) % 256 + S[i], S[j] = S[j], S[i] + + j = 0 + y = 0 + out = [] + + for char in data: + j = (j + 1) % 256 + y = (y + S[j]) % 256 + S[j], S[y] = S[y], S[j] + + out.append(chr(ord(char) ^ S[(S[j] + S[y]) % 256])) + + return ''.join(out) diff --git a/python2/smb/utils/sha256.py b/python2/smb/utils/sha256.py index a13d6bf..6efc8de 100644 --- a/python2/smb/utils/sha256.py +++ b/python2/smb/utils/sha256.py @@ -1,4 +1,3 @@ -#!/usr/bin/python __author__ = 'Thomas Dixon' __license__ = 'MIT' diff --git a/python2/tests/DirectSMBConnectionTests/test_auth.py b/python2/tests/DirectSMBConnectionTests/test_auth.py index 3af7843..d595f87 100644 --- a/python2/tests/DirectSMBConnectionTests/test_auth.py +++ b/python2/tests/DirectSMBConnectionTests/test_auth.py @@ -4,40 +4,83 @@ from nose.tools import with_setup from smb import smb_structs -conn = None +conn, conn2, conn3 = None, None, None def teardown_func(): - global conn - conn.close() + global conn, conn2, conn3 + if conn: + conn.close() + if conn2: + conn2.close() + if conn3: + conn3.close(); @with_setup(teardown = teardown_func) def test_NTLMv1_auth_SMB1(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) + +@with_setup(teardown = teardown_func) +def test_NTLMv1_auth_SMB1_callable_password(): + global conn, conn2, conn3 + smb_structs.SUPPORT_SMB2 = False + info = getConnectionInfo() + conn = SMBConnection(info['user'], lambda: info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], lambda: 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', lambda: 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) @with_setup(teardown = teardown_func) def test_NTLMv2_auth_SMB1(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) @with_setup(teardown = teardown_func) def test_NTLMv1_auth_SMB2(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) @with_setup(teardown = teardown_func) def test_NTLMv2_auth_SMB2(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) diff --git a/python2/tests/DirectSMBConnectionTests/test_createdeletedirectory.py b/python2/tests/DirectSMBConnectionTests/test_createdeletedirectory.py index 3cbcfe7..b6bc1a3 100644 --- a/python2/tests/DirectSMBConnectionTests/test_createdeletedirectory.py +++ b/python2/tests/DirectSMBConnectionTests/test_createdeletedirectory.py @@ -13,7 +13,7 @@ smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): @@ -21,7 +21,7 @@ smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/test_echo.py b/python2/tests/DirectSMBConnectionTests/test_echo.py index 3375972..f86f8cc 100644 --- a/python2/tests/DirectSMBConnectionTests/test_echo.py +++ b/python2/tests/DirectSMBConnectionTests/test_echo.py @@ -9,7 +9,7 @@ def setup_func(): global conn info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): @@ -22,4 +22,3 @@ data = '%d' % random.randint(1000, 9999) assert conn.echo(data) == data - diff --git a/python2/tests/DirectSMBConnectionTests/test_listpath.py b/python2/tests/DirectSMBConnectionTests/test_listpath.py index 9d706b5..8e882a7 100644 --- a/python2/tests/DirectSMBConnectionTests/test_listpath.py +++ b/python2/tests/DirectSMBConnectionTests/test_listpath.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from smb.SMBConnection import SMBConnection +from smb.smb_constants import * from util import getConnectionInfo from nose.tools import with_setup from smb import smb_structs @@ -11,14 +12,14 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): @@ -45,6 +46,13 @@ assert ( u'Test Folder', True ) in filenames assert ( u'子文件夹', True ) in filenames +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathWithManyFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames)==999 + @with_setup(setup_func_SMB2, teardown_func) def test_listPath_SMB2(): global conn @@ -64,3 +72,86 @@ assert ( u'Test File.txt', False ) in filenames assert ( u'Test Folder', True ) in filenames assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathWithManyFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames)==999 + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterForDirectory_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterForDirectory_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterForFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterForFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterPattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterPattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterUnicodePattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterUnicodePattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames diff --git a/python2/tests/DirectSMBConnectionTests/test_listshares.py b/python2/tests/DirectSMBConnectionTests/test_listshares.py index 67c8378..e13bb87 100644 --- a/python2/tests/DirectSMBConnectionTests/test_listshares.py +++ b/python2/tests/DirectSMBConnectionTests/test_listshares.py @@ -10,14 +10,14 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/test_listsnapshots.py b/python2/tests/DirectSMBConnectionTests/test_listsnapshots.py index dfc661c..ba1890e 100644 --- a/python2/tests/DirectSMBConnectionTests/test_listsnapshots.py +++ b/python2/tests/DirectSMBConnectionTests/test_listsnapshots.py @@ -10,14 +10,14 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/test_rename.py b/python2/tests/DirectSMBConnectionTests/test_rename.py index f9896fa..356c2ad 100644 --- a/python2/tests/DirectSMBConnectionTests/test_rename.py +++ b/python2/tests/DirectSMBConnectionTests/test_rename.py @@ -13,14 +13,14 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/test_retrievefile.py b/python2/tests/DirectSMBConnectionTests/test_retrievefile.py index 023d8ce..10aff75 100644 --- a/python2/tests/DirectSMBConnectionTests/test_retrievefile.py +++ b/python2/tests/DirectSMBConnectionTests/test_retrievefile.py @@ -20,14 +20,14 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/test_storefile.py b/python2/tests/DirectSMBConnectionTests/test_storefile.py index a37c24b..dc8df6f 100644 --- a/python2/tests/DirectSMBConnectionTests/test_storefile.py +++ b/python2/tests/DirectSMBConnectionTests/test_storefile.py @@ -25,7 +25,7 @@ smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): @@ -33,7 +33,7 @@ smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): diff --git a/python2/tests/DirectSMBConnectionTests/util.py b/python2/tests/DirectSMBConnectionTests/util.py index aa9eb41..12f82af 100644 --- a/python2/tests/DirectSMBConnectionTests/util.py +++ b/python2/tests/DirectSMBConnectionTests/util.py @@ -10,7 +10,7 @@ info = { 'server_name': cp.get('server', 'name'), 'server_ip': cp.get('server', 'ip'), - 'server_port': cp.getint('server', 'port'), + 'server_port': cp.getint('server', 'direct_port'), 'client_name': cp.get('client', 'name'), 'user': cp.get('user', 'name'), 'password': cp.get('user', 'password'), diff --git a/python2/tests/README.md b/python2/tests/README.md new file mode 100644 index 0000000..16ee904 --- /dev/null +++ b/python2/tests/README.md @@ -0,0 +1,58 @@ + +Steps to Follow to Run the Unit Tests +===================================== + +## Step 1: Install system dependencies ## + +If you are using Ubuntu 20.04 LTS, you can install the system dependencies with the following command +``` +$> apt-get install virtualenv python-dev gcc g++ make automake autoconf +``` +For other distributions, you can use their package managers and install the system dependencies (although the package names might differ slightly). + +## Step 2: Setup python virtualenv ## + +We will create a python2 virtualenv and install the python dependencies for testing in the "venv2" folder. + +``` +$> cd /python2 +$> virtualenv -p /usr/bin/python2 venv2 +$> source venv2/bin/activate +$venv2> pip install nose pyasn1 twisted +``` + +## Step 3: Setup shared folder on your remote SMB server ## + +Prepare a shared folder called "smbtest" on your remote SMB server (Windows or Samba). + +Then, download [smbtest.zip](https://miketeo.net/files/Projects/pysmb/smbtest.zip) and unzip the contents of this zip file in the shared folder. + +You should also configure a user on the SMB server with read-write access to the "smbtest" folder. + +## Step 4: Update connection details in connection.ini ## + +In the same folder where you are viewing this readme file, there should be an ini file called "connection.ini". Edit this file's connection details to match the shared folder's access information. + +## Step 5: Run the unit tests in the python2 folder ## + +Before running the tests, the venv2 virtualenv must be activated. +``` +$> cd /python2 +$> source venv2/bin/activate +``` + +To run all the tests: +``` + $venv2> nosetests -v tests +``` + +To selectively run some tests: +``` + $venv2> nosetests -v tests/SMBConnectionTests + $venv2> nosetests -v tests/SMBConnectionTests/test_rename.py + $venv2> nosetests -v tests/SMBConnectionTests/test_rename.py:test_rename_english_file_SMB1 +``` + +For more information, please consult the [documentation for nose](https://nose.readthedocs.io/). + + diff --git a/python2/tests/README_1st.txt b/python2/tests/README_1st.txt deleted file mode 100644 index aa06a7c..0000000 --- a/python2/tests/README_1st.txt +++ /dev/null @@ -1,32 +0,0 @@ - -Steps to Follow to Run the Unit Tests -===================================== - -1a. Install Nose Testing Framework -All the unit tests here are designed to be conducted with the nose testing framework. -You can install the latest nose testing framework by running: easy_install nose -For more information on nose testing, please visit http://readthedocs.org/docs/nose/en/latest/ - -1b. Install the Twisted framework -If you need to test the SMB/NetBIOS protocol implementations for Twisted framework, -you should install the Twisted framework from http://twistedmatrix.com/ -or by running: easy_install Twisted -Without the Twisted framework, the Twisted tests will fail. - -2. Prepare a Shared Folder "smbtest" on a Remote Server -To run the unit tests here, besides installing the nose testing framework, you will -also need to prepare a shared folder on a remote server. -pysmb has been tested against Samba 3.x, Windows XP SP3 and Windows Vista. -The shared folder must be named "smbtest". - -3. Unzip smbtest.zip in the Shared Folder -In the same folder where you are viewing this readme file, there should be a zip file -called "smbtest.zip". Unzip the contents of this zip file in the shared folder. - -4. Update Connection Details in connection.ini -In the same folder where you are viewing this readme file, there should be an ini file -called "connection.ini". Edit this file's connection details to match the shared folder's -access information. - -5. Run the Unit Tests -Just run: nosetests diff --git a/python2/tests/SMBConnectionTests/test_auth.py b/python2/tests/SMBConnectionTests/test_auth.py index 3d55f20..211e079 100644 --- a/python2/tests/SMBConnectionTests/test_auth.py +++ b/python2/tests/SMBConnectionTests/test_auth.py @@ -4,11 +4,16 @@ from nose.tools import with_setup from smb import smb_structs -conn = None +conn, conn2, conn3 = None, None, None def teardown_func(): - global conn - conn.close() + global conn, conn2, conn3 + if conn: + conn.close() + if conn2: + conn2.close() + if conn3: + conn3.close(); @with_setup(teardown = teardown_func) def test_NTLMv1_auth_SMB1(): @@ -18,6 +23,12 @@ conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = False) assert conn.connect(info['server_ip'], info['server_port']) + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False) + assert not conn3.connect(info['server_ip'], info['server_port']) + @with_setup(teardown = teardown_func) def test_NTLMv2_auth_SMB1(): global conn @@ -25,6 +36,12 @@ info = getConnectionInfo() conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True) + assert not conn3.connect(info['server_ip'], info['server_port']) @with_setup(teardown = teardown_func) def test_NTLMv1_auth_SMB2(): @@ -34,6 +51,12 @@ conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = False) assert conn.connect(info['server_ip'], info['server_port']) + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False) + assert not conn3.connect(info['server_ip'], info['server_port']) + @with_setup(teardown = teardown_func) def test_NTLMv2_auth_SMB2(): global conn @@ -41,3 +64,9 @@ info = getConnectionInfo() conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True) + assert not conn3.connect(info['server_ip'], info['server_port']) diff --git a/python2/tests/SMBConnectionTests/test_deletepattern.py b/python2/tests/SMBConnectionTests/test_deletepattern.py index 23b0774..7a2689d 100644 --- a/python2/tests/SMBConnectionTests/test_deletepattern.py +++ b/python2/tests/SMBConnectionTests/test_deletepattern.py @@ -30,17 +30,24 @@ conn.close() @with_setup(setup_func_SMB1, teardown_func) -def test_delete_SMB1(): - global conn - - path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) - conn.createDirectory('smbtest', path) - - for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: - conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) - - results = conn.listPath('smbtest', path) - filenames = map(lambda r: r.filename, results) +def test_delete_without_subfolder_SMB1(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, StringIO("0123456789")) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' in filenames assert 'aaBest.txt' in filenames assert 'aaTest.bin' in filenames @@ -51,6 +58,8 @@ results = conn.listPath('smbtest', path) filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' not in filenames assert 'aaBest.txt' not in filenames assert 'aaTest.bin' in filenames @@ -61,25 +70,80 @@ results = conn.listPath('smbtest', path) filenames = map(lambda r: r.filename, results) - assert 'aaTest.bin' not in filenames - assert 'aaBest.bin' in filenames - assert 'random.txt' in filenames - - conn.deleteFiles('smbtest', path+'/*') - conn.deleteDirectory('smbtest', path) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + +@with_setup(setup_func_SMB1, teardown_func) +def test_delete_with_subfolder_SMB1(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, StringIO("0123456789")) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' in filenames + assert 'aaBest.txt' in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aa*.txt', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' not in filenames + assert 'aaBest.txt' not in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aaTest.*', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' not in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + @with_setup(setup_func_SMB2, teardown_func) -def test_delete_SMB2(): - global conn - - path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) - conn.createDirectory('smbtest', path) - - for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: - conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) - - results = conn.listPath('smbtest', path) - filenames = map(lambda r: r.filename, results) +def test_delete_without_subfolder_SMB2(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, StringIO("0123456789")) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' in filenames assert 'aaBest.txt' in filenames assert 'aaTest.bin' in filenames @@ -90,6 +154,8 @@ results = conn.listPath('smbtest', path) filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' not in filenames assert 'aaBest.txt' not in filenames assert 'aaTest.bin' in filenames @@ -100,6 +166,55 @@ results = conn.listPath('smbtest', path) filenames = map(lambda r: r.filename, results) - assert 'aaTest.bin' not in filenames - assert 'aaBest.bin' in filenames - assert 'random.txt' in filenames + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_delete_with_subfolder_SMB2(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, StringIO("0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, StringIO("0123456789")) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' in filenames + assert 'aaBest.txt' in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aa*.txt', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' not in filenames + assert 'aaBest.txt' not in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aaTest.*', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = map(lambda r: r.filename, results) + assert 'aaTest.Folder' not in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames diff --git a/python2/tests/SMBConnectionTests/test_listpath.py b/python2/tests/SMBConnectionTests/test_listpath.py index 9d706b5..82ad2fd 100644 --- a/python2/tests/SMBConnectionTests/test_listpath.py +++ b/python2/tests/SMBConnectionTests/test_listpath.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from smb.SMBConnection import SMBConnection +from smb.smb_constants import * from util import getConnectionInfo from nose.tools import with_setup from smb import smb_structs @@ -45,6 +46,13 @@ assert ( u'Test Folder', True ) in filenames assert ( u'子文件夹', True ) in filenames +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathWithManyFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames)==999 + @with_setup(setup_func_SMB2, teardown_func) def test_listPath_SMB2(): global conn @@ -64,3 +72,98 @@ assert ( u'Test File.txt', False ) in filenames assert ( u'Test Folder', True ) in filenames assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathWithManyFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames)==999 + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterForDirectory_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterForDirectory_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterForFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterForFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterPattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterPattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterUnicodePattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterUnicodePattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB1, teardown_func) +def test_listPathFilterEmptyList_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive', pattern = '*.abc') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + +@with_setup(setup_func_SMB2, teardown_func) +def test_listPathFilterEmptyList_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive', pattern = '*.abc') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) diff --git a/python2/tests/SMBConnectionTests/test_security.py b/python2/tests/SMBConnectionTests/test_security.py new file mode 100644 index 0000000..205b119 --- /dev/null +++ b/python2/tests/SMBConnectionTests/test_security.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import os, tempfile +from StringIO import StringIO +from smb.SMBConnection import SMBConnection +from util import getConnectionInfo +from nose.tools import with_setup +from smb import smb_structs + +try: + import hashlib + def MD5(): return hashlib.md5() +except ImportError: + import md5 + def MD5(): return md5.new() + +conn = None + +def setup_func_SMB2(): + global conn + smb_structs.SUPPORT_SMB2 = True + info = getConnectionInfo() + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + assert conn.connect(info['server_ip'], info['server_port']) + +def teardown_func(): + global conn + conn.close() + +@with_setup(setup_func_SMB2, teardown_func) +def test_security_SMB2(): + global conn + # TODO: Need a way to setup the environment on the remote server and perform some verification on the returned results. + attributes = conn.getSecurity('smbtest', '/rfc1001.txt') diff --git a/python2/tests/SMBConnectionTests/test_with_context.py b/python2/tests/SMBConnectionTests/test_with_context.py new file mode 100644 index 0000000..58d9567 --- /dev/null +++ b/python2/tests/SMBConnectionTests/test_with_context.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from smb.SMBConnection import SMBConnection +from .util import getConnectionInfo + +def test_context(): + info = getConnectionInfo() + with SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) as conn: + assert conn.connect(info['server_ip'], info['server_port']) + + assert conn.sock is None diff --git a/python2/tests/connection.ini b/python2/tests/connection.ini index 96d2652..d8d249d 100644 --- a/python2/tests/connection.ini +++ b/python2/tests/connection.ini @@ -3,6 +3,7 @@ name = SERVER ip = 192.168.1.1 port = 139 +direct_port = 445 [client] name = TESTCLIENT diff --git a/python2/tests/smbtest.7z b/python2/tests/smbtest.7z deleted file mode 100644 index c5c9f42..0000000 Binary files a/python2/tests/smbtest.7z and /dev/null differ diff --git a/python2/tests/test_security_descriptors.py b/python2/tests/test_security_descriptors.py new file mode 100644 index 0000000..f81be4b --- /dev/null +++ b/python2/tests/test_security_descriptors.py @@ -0,0 +1,139 @@ +import binascii + +from smb import security_descriptors as sd +from smb import smb_constants as sc + + +def test_sid_string_representation(): + sid = sd.SID(1, 5, [2, 3, 4]) + assert str(sid) == "S-1-5-2-3-4" + sid = sd.SID(1, 2**32 + 3, []) + assert str(sid) == "S-1-0x100000003" + sid = sd.SID(1, 2**32, [3, 2, 1]) + assert str(sid) == "S-1-0x100000000-3-2-1" + + +def test_sid_binary_parsing(): + raw_sid = binascii.unhexlify(""" + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, ' \n')) + assert str(sd.SID.from_bytes(raw_sid)) == "S-1-5-21-717312990-3403304746-849770945-1201" + raw_sid += "garbage" + assert str(sd.SID.from_bytes(raw_sid)) == "S-1-5-21-717312990-3403304746-849770945-1201" + sid, tail = sd.SID.from_bytes(raw_sid, return_tail=True) + assert str(sid) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert tail == "garbage" + + +def test_ace_binary_parsing(): + raw_ace = binascii.unhexlify(""" + 00 10 24 00 ff 01 1f 00 01 05 00 00 00 00 00 05 + 15 00 00 00 de 53 c1 2a 2a 4f da ca c1 79 a6 32 + 6e 04 00 00 + """.translate(None, ' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1134" + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.WRITE_OWNER | sc.WRITE_DAC + | sc.READ_CONTROL | sc.DELETE | sc.FILE_READ_DATA + | sc.FILE_WRITE_DATA | sc.FILE_APPEND_DATA + | sc.FILE_READ_EA | sc.FILE_WRITE_EA | sc.FILE_EXECUTE + | sc.FILE_DELETE_CHILD | sc.FILE_READ_ATTRIBUTES + | sc.FILE_WRITE_ATTRIBUTES) + assert not ace.additional_data + + raw_ace = binascii.unhexlify(""" + 00 13 18 00 a9 00 12 00 01 02 00 00 00 00 00 05 + 20 00 00 00 21 02 00 00 + """.translate(None, ' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-32-545" + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert ace.flags == (sd.ACE_FLAG_INHERITED | sd.ACE_FLAG_CONTAINER_INHERIT + | sd.ACE_FLAG_OBJECT_INHERIT) + assert ace.mask == (sc.SYNCHRONIZE | sc.READ_CONTROL | sc.FILE_READ_DATA + | sc.FILE_READ_EA | sc.FILE_EXECUTE + | sc.FILE_READ_ATTRIBUTES) + assert not ace.additional_data + + raw_ace = binascii.unhexlify(""" + 01 03 24 00 a9 00 02 00 01 05 00 00 00 00 00 05 + 15 00 00 00 de 53 c1 2a 2a 4f da ca c1 79 a6 32 + 6c 04 00 00 + """.translate(None, ' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1132" + assert ace.type == sd.ACE_TYPE_ACCESS_DENIED + assert ace.flags == (sd.ACE_FLAG_CONTAINER_INHERIT + | sd.ACE_FLAG_OBJECT_INHERIT) + assert ace.mask == (sc.READ_CONTROL | sc.FILE_READ_DATA | sc.FILE_READ_EA + | sc.FILE_EXECUTE | sc.FILE_READ_ATTRIBUTES) + assert not ace.additional_data + + +def test_acl_binary_parsing(): + raw_acl = binascii.unhexlify(""" + 02 00 70 00 04 00 00 00 00 10 18 00 89 00 10 00 + 01 02 00 00 00 00 00 05 20 00 00 00 21 02 00 00 + 00 10 14 00 ff 01 1f 00 01 01 00 00 00 00 00 05 + 12 00 00 00 00 10 18 00 ff 01 1f 00 01 02 00 00 + 00 00 00 05 20 00 00 00 20 02 00 00 00 10 24 00 + ff 01 1f 00 01 05 00 00 00 00 00 05 15 00 00 00 + de 53 c1 2a 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, ' \n')) + acl = sd.ACL.from_bytes(raw_acl) + assert acl.revision == 2 + assert len(acl.aces) == 4 + + ace = acl.aces[0] + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert str(ace.sid) == "S-1-5-32-545" + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.FILE_READ_DATA | sc.FILE_READ_EA + | sc.FILE_READ_ATTRIBUTES) + + ace = acl.aces[3] + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.WRITE_OWNER | sc.WRITE_DAC + | sc.READ_CONTROL | sc.DELETE | sc.FILE_READ_DATA + | sc.FILE_WRITE_DATA | sc.FILE_APPEND_DATA + | sc.FILE_READ_EA | sc.FILE_WRITE_EA | sc.FILE_EXECUTE + | sc.FILE_DELETE_CHILD | sc.FILE_READ_ATTRIBUTES + | sc.FILE_WRITE_ATTRIBUTES) + + +def test_descriptor_binary_parsing(): + raw_descriptor = binascii.unhexlify(""" + 01 00 04 84 14 00 00 00 30 00 00 00 00 00 00 00 + 4c 00 00 00 01 05 00 00 00 00 00 05 15 00 00 00 + de 53 c1 2a 2a 4f da ca c1 79 a6 32 b1 04 00 00 + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 01 02 00 00 02 00 70 00 + 04 00 00 00 00 10 18 00 89 00 10 00 01 02 00 00 + 00 00 00 05 20 00 00 00 21 02 00 00 00 10 14 00 + ff 01 1f 00 01 01 00 00 00 00 00 05 12 00 00 00 + 00 10 18 00 ff 01 1f 00 01 02 00 00 00 00 00 05 + 20 00 00 00 20 02 00 00 00 10 24 00 ff 01 1f 00 + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, ' \n')) + descriptor = sd.SecurityDescriptor.from_bytes(raw_descriptor) + assert descriptor.flags == (sd.SECURITY_DESCRIPTOR_SELF_RELATIVE + | sd.SECURITY_DESCRIPTOR_DACL_PRESENT + | sd.SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED) + assert descriptor.dacl is not None + assert descriptor.sacl is None + assert str(descriptor.owner) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert str(descriptor.group) == "S-1-5-21-717312990-3403304746-849770945-513" + + acl = descriptor.dacl + assert acl.revision == 2 + assert len(acl.aces) == 4 + assert str(acl.aces[0].sid) == sd.SID_BUILTIN_USERS + assert str(acl.aces[1].sid) == sd.SID_LOCAL_SYSTEM + assert str(acl.aces[2].sid) == sd.SID_BUILTIN_ADMINISTRATORS + assert str(acl.aces[3].sid) == "S-1-5-21-717312990-3403304746-849770945-1201" diff --git a/python3/nmb/base.py b/python3/nmb/base.py index e161a18..f3e7651 100644 --- a/python3/nmb/base.py +++ b/python3/nmb/base.py @@ -77,6 +77,9 @@ self.onNMBSessionOK() elif packet.type == NEGATIVE_SESSION_RESPONSE: self.onNMBSessionFailed() + elif packet.type == SESSION_KEEPALIVE: + # Discard keepalive packets - [RFC1002]: 5.2.2.1 + pass else: self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type) @@ -153,21 +156,25 @@ opcode = (code >> 11) & 0x0F flags = (code >> 4) & 0x7F rcode = code & 0x0F - numnames = data[self.HEADER_STRUCT_SIZE + 44] - if numnames > 0: - ret = [ ] - offset = self.HEADER_STRUCT_SIZE + 45 + try: + numnames = data[self.HEADER_STRUCT_SIZE + 44] - for i in range(0, numnames): - mynme = data[offset:offset + 15] - mynme = mynme.strip() - ret.append(( str(mynme, 'ascii'), data[offset+15] )) - offset += 18 + if numnames > 0: + ret = [ ] + offset = self.HEADER_STRUCT_SIZE + 45 - return trn_id, ret - else: - return trn_id, None + for i in range(0, numnames): + mynme = data[offset:offset + 15] + mynme = mynme.strip() + ret.append(( str(mynme, 'ascii'), data[offset+15] )) + offset += 18 + + return trn_id, ret + except IndexError: + pass + + return trn_id, None # # Contributed by Jason Anderson diff --git a/python3/pysmb.egg-info/PKG-INFO b/python3/pysmb.egg-info/PKG-INFO new file mode 100644 index 0000000..eb471d0 --- /dev/null +++ b/python3/pysmb.egg-info/PKG-INFO @@ -0,0 +1,26 @@ +Metadata-Version: 1.1 +Name: pysmb +Version: 1.2.6 +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 +Author-email: miketeo@miketeo.net +License: zlib/libpng +Description: pysmb is an experimental SMB/CIFS library written in Python. It implements the client-side SMB/CIFS protocol which allows your Python application to access and transfer files to/from SMB/CIFS shared folders like your Windows file sharing and Samba folders. +Keywords: windows samba cifs sharing ftp smb linux +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Win32 (MS Windows) +Classifier: Environment :: Console +Classifier: License :: OSI Approved :: zlib/libpng License +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 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Networking diff --git a/python3/pysmb.egg-info/SOURCES.txt b/python3/pysmb.egg-info/SOURCES.txt new file mode 100644 index 0000000..ea74c4d --- /dev/null +++ b/python3/pysmb.egg-info/SOURCES.txt @@ -0,0 +1,245 @@ +CHANGELOG +LICENSE +MANIFEST.in +README.txt +setup.py +docs/doctrees/environment.pickle +docs/doctrees/extending.doctree +docs/doctrees/index.doctree +docs/doctrees/upgrading.doctree +docs/doctrees/api/nmb_NBNSProtocol.doctree +docs/doctrees/api/nmb_NetBIOS.doctree +docs/doctrees/api/smb_SMBConnection.doctree +docs/doctrees/api/smb_SMBHandler.doctree +docs/doctrees/api/smb_SMBProtocolFactory.doctree +docs/doctrees/api/smb_SharedDevice.doctree +docs/doctrees/api/smb_SharedFile.doctree +docs/doctrees/api/smb_exceptions.doctree +docs/doctrees/api/smb_security_descriptors.doctree +docs/html/.buildinfo +docs/html/extending.html +docs/html/genindex.html +docs/html/index.html +docs/html/objects.inv +docs/html/py-modindex.html +docs/html/search.html +docs/html/searchindex.js +docs/html/upgrading.html +docs/html/_modules/index.html +docs/html/_modules/nmb/NetBIOS.html +docs/html/_modules/nmb/NetBIOSProtocol.html +docs/html/_modules/smb/SMBConnection.html +docs/html/_modules/smb/SMBProtocol.html +docs/html/_modules/smb/base.html +docs/html/_modules/smb/security_descriptors.html +docs/html/_modules/smb/smb_structs.html +docs/html/_sources/extending.txt +docs/html/_sources/index.txt +docs/html/_sources/upgrading.txt +docs/html/_sources/api/nmb_NBNSProtocol.txt +docs/html/_sources/api/nmb_NetBIOS.txt +docs/html/_sources/api/smb_SMBConnection.txt +docs/html/_sources/api/smb_SMBHandler.txt +docs/html/_sources/api/smb_SMBProtocolFactory.txt +docs/html/_sources/api/smb_SharedDevice.txt +docs/html/_sources/api/smb_SharedFile.txt +docs/html/_sources/api/smb_exceptions.txt +docs/html/_sources/api/smb_security_descriptors.txt +docs/html/_static/ajax-loader.gif +docs/html/_static/basic.css +docs/html/_static/comment-bright.png +docs/html/_static/comment-close.png +docs/html/_static/comment.png +docs/html/_static/contents.png +docs/html/_static/doctools.js +docs/html/_static/down-pressed.png +docs/html/_static/down.png +docs/html/_static/file.png +docs/html/_static/jquery.js +docs/html/_static/minus.png +docs/html/_static/navigation.png +docs/html/_static/plus.png +docs/html/_static/pygments.css +docs/html/_static/searchtools.js +docs/html/_static/sphinxdoc.css +docs/html/_static/underscore.js +docs/html/_static/up-pressed.png +docs/html/_static/up.png +docs/html/_static/websupport.js +docs/html/api/nmb_NBNSProtocol.html +docs/html/api/nmb_NetBIOS.html +docs/html/api/smb_SMBConnection.html +docs/html/api/smb_SMBHandler.html +docs/html/api/smb_SMBProtocolFactory.html +docs/html/api/smb_SharedDevice.html +docs/html/api/smb_SharedFile.html +docs/html/api/smb_exceptions.html +docs/html/api/smb_security_descriptors.html +python2/nmb/NetBIOS.py +python2/nmb/NetBIOSProtocol.py +python2/nmb/__init__.py +python2/nmb/base.py +python2/nmb/nmb_constants.py +python2/nmb/nmb_structs.py +python2/nmb/utils.py +python2/smb/SMBConnection.py +python2/smb/SMBHandler.py +python2/smb/SMBProtocol.py +python2/smb/__init__.py +python2/smb/base.py +python2/smb/ntlm.py +python2/smb/security_descriptors.py +python2/smb/securityblob.py +python2/smb/smb2_constants.py +python2/smb/smb2_structs.py +python2/smb/smb_constants.py +python2/smb/smb_structs.py +python2/smb/utils/README.txt +python2/smb/utils/U32.py +python2/smb/utils/__init__.py +python2/smb/utils/md4.py +python2/smb/utils/pyDes.py +python2/smb/utils/rc4.py +python2/smb/utils/sha256.py +python2/tests/README.md +python2/tests/__init__.py +python2/tests/connection.ini +python2/tests/test_ntlm.py +python2/tests/test_security_descriptors.py +python2/tests/test_securityblob.py +python2/tests/DirectSMBConnectionTests/__init__.py +python2/tests/DirectSMBConnectionTests/test_SMBHandler.py +python2/tests/DirectSMBConnectionTests/test_auth.py +python2/tests/DirectSMBConnectionTests/test_createdeletedirectory.py +python2/tests/DirectSMBConnectionTests/test_echo.py +python2/tests/DirectSMBConnectionTests/test_listpath.py +python2/tests/DirectSMBConnectionTests/test_listshares.py +python2/tests/DirectSMBConnectionTests/test_listsnapshots.py +python2/tests/DirectSMBConnectionTests/test_rename.py +python2/tests/DirectSMBConnectionTests/test_retrievefile.py +python2/tests/DirectSMBConnectionTests/test_storefile.py +python2/tests/DirectSMBConnectionTests/util.py +python2/tests/DirectSMBTwistedTests/test_auth.py +python2/tests/DirectSMBTwistedTests/test_createdeletedirectory.py +python2/tests/DirectSMBTwistedTests/test_echo.py +python2/tests/DirectSMBTwistedTests/test_listpath.py +python2/tests/DirectSMBTwistedTests/test_listshares.py +python2/tests/DirectSMBTwistedTests/test_listsnapshots.py +python2/tests/DirectSMBTwistedTests/test_rename.py +python2/tests/DirectSMBTwistedTests/test_retrievefile.py +python2/tests/DirectSMBTwistedTests/test_storefile.py +python2/tests/DirectSMBTwistedTests/util.py +python2/tests/NetBIOSTests/__init__.py +python2/tests/NetBIOSTests/test_queryname.py +python2/tests/NetBIOSTwistedTests/__init__.py +python2/tests/NetBIOSTwistedTests/test_queryname.py +python2/tests/SMBConnectionTests/__init__.py +python2/tests/SMBConnectionTests/test_SMBHandler.py +python2/tests/SMBConnectionTests/test_auth.py +python2/tests/SMBConnectionTests/test_createdeletedirectory.py +python2/tests/SMBConnectionTests/test_deletepattern.py +python2/tests/SMBConnectionTests/test_echo.py +python2/tests/SMBConnectionTests/test_getattributes.py +python2/tests/SMBConnectionTests/test_listpath.py +python2/tests/SMBConnectionTests/test_listshares.py +python2/tests/SMBConnectionTests/test_listsnapshots.py +python2/tests/SMBConnectionTests/test_rename.py +python2/tests/SMBConnectionTests/test_retrievefile.py +python2/tests/SMBConnectionTests/test_security.py +python2/tests/SMBConnectionTests/test_storefile.py +python2/tests/SMBConnectionTests/test_with_context.py +python2/tests/SMBConnectionTests/util.py +python2/tests/SMBTwistedTests/__init__.py +python2/tests/SMBTwistedTests/test_auth.py +python2/tests/SMBTwistedTests/test_createdeletedirectory.py +python2/tests/SMBTwistedTests/test_echo.py +python2/tests/SMBTwistedTests/test_getattributes.py +python2/tests/SMBTwistedTests/test_listpath.py +python2/tests/SMBTwistedTests/test_listshares.py +python2/tests/SMBTwistedTests/test_listsnapshots.py +python2/tests/SMBTwistedTests/test_rename.py +python2/tests/SMBTwistedTests/test_retrievefile.py +python2/tests/SMBTwistedTests/test_storefile.py +python2/tests/SMBTwistedTests/util.py +python2/tests/SupportFiles/binary.dat +python3/nmb/NetBIOS.py +python3/nmb/NetBIOSProtocol.py +python3/nmb/__init__.py +python3/nmb/base.py +python3/nmb/nmb_constants.py +python3/nmb/nmb_structs.py +python3/nmb/utils.py +python3/pysmb.egg-info/PKG-INFO +python3/pysmb.egg-info/SOURCES.txt +python3/pysmb.egg-info/dependency_links.txt +python3/pysmb.egg-info/requires.txt +python3/pysmb.egg-info/top_level.txt +python3/smb/SMBConnection.py +python3/smb/SMBHandler.py +python3/smb/SMBProtocol.py +python3/smb/__init__.py +python3/smb/base.py +python3/smb/ntlm.py +python3/smb/security_descriptors.py +python3/smb/securityblob.py +python3/smb/smb2_constants.py +python3/smb/smb2_structs.py +python3/smb/smb_constants.py +python3/smb/smb_structs.py +python3/smb/utils/U32.py +python3/smb/utils/__init__.py +python3/smb/utils/md4.py +python3/smb/utils/pyDes.py +python3/smb/utils/rc4.py +python3/smb/utils/sha256.py +python3/tests/README.md +python3/tests/__init__.py +python3/tests/connection.ini +python3/tests/test_ntlm.py +python3/tests/test_security_descriptors.py +python3/tests/test_securityblob.py +python3/tests/DirectSMBConnectionTests/__init__.py +python3/tests/DirectSMBConnectionTests/test_auth.py +python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py +python3/tests/DirectSMBConnectionTests/test_echo.py +python3/tests/DirectSMBConnectionTests/test_listpath.py +python3/tests/DirectSMBConnectionTests/test_listshares.py +python3/tests/DirectSMBConnectionTests/test_listsnapshots.py +python3/tests/DirectSMBConnectionTests/test_rename.py +python3/tests/DirectSMBConnectionTests/test_retrievefile.py +python3/tests/DirectSMBConnectionTests/test_storefile.py +python3/tests/DirectSMBConnectionTests/util.py +python3/tests/NetBIOSTests/__init__.py +python3/tests/NetBIOSTests/test_queryname.py +python3/tests/SMBConnectionTests/__init__.py +python3/tests/SMBConnectionTests/test_SMBHandler.py +python3/tests/SMBConnectionTests/test_auth.py +python3/tests/SMBConnectionTests/test_createdeletedirectory.py +python3/tests/SMBConnectionTests/test_deletepattern.py +python3/tests/SMBConnectionTests/test_echo.py +python3/tests/SMBConnectionTests/test_getattributes.py +python3/tests/SMBConnectionTests/test_listpath.py +python3/tests/SMBConnectionTests/test_listshares.py +python3/tests/SMBConnectionTests/test_listsnapshots.py +python3/tests/SMBConnectionTests/test_rename.py +python3/tests/SMBConnectionTests/test_retrievefile.py +python3/tests/SMBConnectionTests/test_storefile.py +python3/tests/SMBConnectionTests/test_with_context.py +python3/tests/SMBConnectionTests/util.py +python3/tests/SupportFiles/binary.dat +sphinx/Makefile +sphinx/make.bat +sphinx/requirements.txt +sphinx/source/conf.py +sphinx/source/extending.rst +sphinx/source/index.rst +sphinx/source/upgrading.rst +sphinx/source/api/nmb_NBNSProtocol.rst +sphinx/source/api/nmb_NetBIOS.rst +sphinx/source/api/smb_SMBConnection.rst +sphinx/source/api/smb_SMBHandler.rst +sphinx/source/api/smb_SMBProtocolFactory.rst +sphinx/source/api/smb_SharedDevice.rst +sphinx/source/api/smb_SharedFile.rst +sphinx/source/api/smb_exceptions.rst +sphinx/source/api/smb_security_descriptors.rst \ No newline at end of file diff --git a/python3/pysmb.egg-info/dependency_links.txt b/python3/pysmb.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python3/pysmb.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python3/pysmb.egg-info/requires.txt b/python3/pysmb.egg-info/requires.txt new file mode 100644 index 0000000..38fe414 --- /dev/null +++ b/python3/pysmb.egg-info/requires.txt @@ -0,0 +1 @@ +pyasn1 diff --git a/python3/pysmb.egg-info/top_level.txt b/python3/pysmb.egg-info/top_level.txt new file mode 100644 index 0000000..65ad110 --- /dev/null +++ b/python3/pysmb.egg-info/top_level.txt @@ -0,0 +1,2 @@ +nmb +smb diff --git a/python3/smb/SMBConnection.py b/python3/smb/SMBConnection.py index 172d203..d3f9ea2 100644 --- a/python3/smb/SMBConnection.py +++ b/python3/smb/SMBConnection.py @@ -21,6 +21,7 @@ Create a new SMBConnection instance. *username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server. + *password* can be a string or a callable returning a string. File operations can only be proceeded after the connection has been authenticated successfully. Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication. @@ -29,7 +30,7 @@ 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 ``\/:*?";|+`` + 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. @@ -72,6 +73,15 @@ total_sent = total_sent + sent # + # Support for "with" context + # + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + # # Misc Properties # @@ -153,15 +163,23 @@ return results def listPath(self, service_name, path, - search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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` instances. """ @@ -245,6 +263,37 @@ self.is_busy = True try: self._getAttributes(service_name, path, cb, eb, timeout) + while self.is_busy: + self._pollForNetBIOSPacket(timeout) + finally: + self.is_busy = False + + return results[0] + + def getSecurity(self, service_name, path, timeout = 30): + """ + Retrieve the security descriptor of the file at *path* on the *service_name*. + + :param string/unicode service_name: the name of the shared folder for the *path* + :param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure` will be raised. + :return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file. + """ + if not self.sock: + raise NotConnectedError('Not connected to server') + + results = [ ] + + def cb(info): + self.is_busy = False + results.append(info) + + def eb(failure): + self.is_busy = False + raise failure + + self.is_busy = True + try: + self._getSecurity(service_name, path, cb, eb, timeout) while self.is_busy: self._pollForNetBIOSPacket(timeout) finally: @@ -350,9 +399,11 @@ return results[0] - def deleteFiles(self, service_name, path_file_pattern, timeout = 30): + def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30): """ Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request. + + If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively. :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. @@ -372,23 +423,27 @@ 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 - - def resetFileAttributes(self, service_name, path_file_pattern, timeout = 30): + self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout) + while self.is_busy: + self._pollForNetBIOSPacket(timeout) + finally: + self.is_busy = False + + def resetFileAttributes(self, service_name, path_file_pattern, file_attributes = ATTR_NORMAL, timeout = 30): """ Reset file attributes of one or more regular files or folders. It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request. This function is very helpful when deleting files/folders that are read-only. - 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) - + By default, it sets the ATTR_NORMAL flag, therefore clearing all other flags. + (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information) + + Note: this function is currently only implemented for SMB2! + :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. Wildcards may be used in the filename component of the path. If your path/filename contains non-English characters, you must pass in an unicode string. + :param int file_attributes: The desired file attributes to set. Defaults to `ATTR_NORMAL`. :return: None """ if not self.sock: @@ -403,12 +458,11 @@ 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 - + self._resetFileAttributes(service_name, path_file_pattern, cb, eb, file_attributes, timeout) + while self.is_busy: + self._pollForNetBIOSPacket(timeout) + finally: + self.is_busy = False def createDirectory(self, service_name, path, timeout = 30): """ @@ -496,7 +550,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :return: The *data* parameter """ if not self.sock: diff --git a/python3/smb/SMBHandler.py b/python3/smb/SMBHandler.py index 042d356..0241a01 100644 --- a/python3/smb/SMBHandler.py +++ b/python3/smb/SMBHandler.py @@ -1,4 +1,3 @@ - import os, sys, socket, urllib.request, urllib.error, urllib.parse, mimetypes, email, tempfile from urllib.parse import (unwrap, unquote, splittype, splithost, quote, splitport, splittag, splitattr, splituser, splitpasswd, splitvalue) @@ -26,11 +25,14 @@ port = int(port) # username/password handling + user, host = splituser(host) + if user: user, passwd = splitpasswd(user) else: passwd = None + host = unquote(host) user = user or '' @@ -41,12 +43,16 @@ passwd = passwd or '' myname = MACHINE_NAME or self.generateClientMachineName() - n = NetBIOS() - names = n.queryIPForName(host) - if names: - server_name = names[0] - else: - raise urllib.error.URLError('SMB error: Hostname does not reply back with its machine name') + server_name,host = host.split(',') if ',' in host else [None,host] + + if server_name is None: + n = NetBIOS() + + names = n.queryIPForName(host) + if names: + server_name = names[0] + else: + raise urllib.error.URLError('SMB error: Hostname does not reply back with its machine name') path, attrs = splitattr(req.selector) if path.startswith('/'): diff --git a/python3/smb/SMBProtocol.py b/python3/smb/SMBProtocol.py index e0089c2..b76aa57 100644 --- a/python3/smb/SMBProtocol.py +++ b/python3/smb/SMBProtocol.py @@ -178,15 +178,23 @@ return d def listPath(self, service_name, path, - search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE, + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL, pattern = '*', timeout = 30): """ Retrieve a directory listing of files/folders at *path* + + For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + + Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN), + system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files + and directories (SMB_FILE_ATTRIBUTE_DIRECTORY). + If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant. + SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants. :param string/unicode service_name: the name of the shared folder for the *path* :param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders. :param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py). - 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` instances. @@ -295,10 +303,12 @@ self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout) return d - def deleteFiles(self, service_name, path_file_pattern, timeout = 30): + def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30): """ Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request. + If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively. + :param string/unicode service_name: Contains the name of the shared folder. :param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name. Wildcards may be used in th filename component of the path. @@ -310,7 +320,7 @@ raise NotConnectedError('Not connected to server') d = defer.Deferred() - self.instance._deleteFiles(service_name, path_file_pattern, d.callback, d.errback, timeout = timeout) + self.instance._deleteFiles(service_name, path_file_pattern, delete_matching_folders, d.callback, d.errback, timeout = timeout) return d def createDirectory(self, service_name, path): @@ -369,7 +379,7 @@ """ 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 bytes data: Data to send to the remote server. Must be a bytes object. :param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method. :return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter. """ diff --git a/python3/smb/base.py b/python3/smb/base.py index a1ec271..15d2903 100644 --- a/python3/smb/base.py +++ b/python3/smb/base.py @@ -5,6 +5,7 @@ from .smb2_constants import * from .smb_structs import * from .smb2_structs import * +from .security_descriptors import SecurityDescriptor from nmb.base import NMBSession from .utils import convertFILETIMEtoEpoch from . import ntlm, securityblob @@ -53,13 +54,14 @@ 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 = username - self.password = password + self._password = password self.domain = 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.async_requests = { } #: AsyncID mapped to _PendingRequest instance self.pending_requests = { } #: MID mapped to _PendingRequest instance self.connected_trees = { } #: Share name mapped to TID self.next_rpc_call_id = 1 #: Next RPC callID value. Not used directly in SMB message. Usually encapsulated in sub-commands under SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2 messages @@ -98,6 +100,11 @@ (self.use_ntlm_v2 and 'v2') or 'v1', (SUPPORT_EXTENDED_SECURITY and 'with') or 'without') + @property + def password(self): + if callable(self._password): + return self._password() + return self._password # # NMBSession Methods @@ -167,6 +174,7 @@ self._listShares = self._listShares_SMB1 self._listPath = self._listPath_SMB1 self._listSnapshots = self._listSnapshots_SMB1 + self._getSecurity = self._getSecurity_SMB1 self._getAttributes = self._getAttributes_SMB1 self._retrieveFile = self._retrieveFile_SMB1 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1 @@ -189,6 +197,7 @@ self._listShares = self._listShares_SMB2 self._listPath = self._listPath_SMB2 self._listSnapshots = self._listSnapshots_SMB2 + self._getSecurity = self._getSecurity_SMB2 self._getAttributes = self._getAttributes_SMB2 self._retrieveFile = self._retrieveFile_SMB2 self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB2 @@ -213,7 +222,7 @@ 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: + if smb_message.command != SMB2_COM_NEGOTIATE: smb_message.session_id = self.session_id if self.is_signing_active: @@ -250,6 +259,19 @@ if result == securityblob.RESULT_ACCEPT_COMPLETED: self.has_authenticated = True self.log.info('Authentication (on SMB2) successful!') + + # [MS-SMB2]: 3.2.5.3.1 + # If the security subsystem indicates that the session was established by an anonymous user, + # Session.SigningRequired MUST be set to FALSE. + # If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the + # SMB2 SESSION_SETUP Response and if Session.SigningRequired is TRUE, this indicates a SESSION_SETUP + # failure and the connection MUST be terminated. If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags + # field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired + # MUST be set to FALSE. + if message.payload.isGuestSession or message.payload.isAnonymousSession: + self.is_signing_active = False + self.log.info('Signing disabled because session is guest/anonymous') + self.onAuthOK() else: raise ProtocolError('SMB2_COM_SESSION_SETUP status is 0 but security blob negResult value is %d' % result, message.raw_data, message) @@ -263,18 +285,58 @@ self._handleSessionChallenge(message, ntlm_token) except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ) as ex: raise ProtocolError(str(ex), message.raw_data, message) - elif message.status == 0xc000006d: # STATUS_LOGON_FAILURE + elif (message.status == 0xc000006d # STATUS_LOGON_FAILURE + or message.status == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status == 0xc000006a):# STATUS_WRONG_PASSWORD self.has_authenticated = False self.log.info('Authentication (on SMB2) failed. Please check username and password.') self.onAuthFailed() + elif (message.status == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Not allowed.') + self.onAuthFailed() + elif message.status == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (on SMB2) failed. Workstation not trusted.') + 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 + if message.isAsync: + if message.status == 0x00000103: # STATUS_PENDING + req = self.pending_requests.pop(message.mid, None) + if req: + self.async_requests[message.async_id] = req + else: # All other status including SUCCESS + req = self.async_requests.pop(message.async_id, None) + if req: + req.callback(message, **req.kwargs) + return True + else: + req = self.pending_requests.pop(message.mid, None) + if req: + req.callback(message, **req.kwargs) + return True def _updateServerInfo_SMB2(self, payload): @@ -309,12 +371,13 @@ 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) + ntlm_data, session_signing_key = ntlm.generateAuthenticateMessage(server_flags, + nt_challenge_response, + lm_challenge_response, + session_key, + self.username, + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -334,7 +397,10 @@ if self.is_signing_active: self.log.info("SMB signing activated. All SMB messages will be signed.") - self.signing_session_key = (session_key + b'\0'*16)[:16] + self.signing_session_key = session_signing_key + if self.log.isEnabledFor(logging.DEBUG): + self.log.info("SMB signing key is %s", binascii.hexlify(self.signing_session_key)) + if self.capabilities & CAP_EXTENDED_SECURITY: self.signing_challenge_response = None else: @@ -363,7 +429,7 @@ m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback, tid = tid) messages_history.append(m) def connectSrvSvcCB(create_message, **kwargs): @@ -385,9 +451,9 @@ 01 00 00 00 """.replace(b' ', b'').replace(b'\n', b'')) m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0)) - m.tid = create_message.tid + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) messages_history.append(m) else: errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history)) @@ -396,12 +462,12 @@ 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 + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint') def rpcReadCB(read_message, **kwargs): messages_history.append(read_message) @@ -429,12 +495,12 @@ 00 00 00 00 ff ff ff ff 08 00 02 00 00 00 00 00 """.replace(b' ', b'').replace(b'\n', b'')) m = SMB2Message(SMB2IoctlRequest(kwargs['fid'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes)) - m.tid = read_message.tid + m.tid = kwargs['tid'] self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid']) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, tid = kwargs['tid'], 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') + closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint') def listShareResultsCB(result_message, **kwargs): messages_history.append(result_message) @@ -443,13 +509,11 @@ data_bytes = result_message.payload.out_data if 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], data_bytes) + else: + decodeResults(kwargs['tid'], kwargs['fid'], data_bytes) + else: + closeFid(kwargs['tid'], kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) def decodeResults(tid, fid, data_bytes): @@ -488,20 +552,19 @@ 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) + tid = tid, 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 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']) + sendReadRequest(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + closeFid(kwargs['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): @@ -566,39 +629,44 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: - sendQuery(create_message.tid, create_message.payload.fid, b'') + sendQuery(kwargs['tid'], create_message.payload.fid, b'') + elif create_message.status == 0xC0000034: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to list %s on %s: Path not found' % ( path, service_name ), messages_history)) else: errback(OperationFailure('Failed to list %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) def sendQuery(tid, fid, data_buf): m = SMB2Message(SMB2QueryDirectoryRequest(fid, pattern, - info_class = 0x03, # FileBothDirectoryInformation + info_class = 0x25, # FileIdBothDirectoryInformation 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = tid, 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) + sendQuery(kwargs['tid'], kwargs['fid'], data_buf) + elif query_message.status == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing + closeFid(kwargs['tid'], kwargs['fid'], results = results) elif query_message.status == 0x80000006: # STATUS_NO_MORE_FILES - closeFid(query_message.tid, kwargs['fid'], results = results) - else: - closeFid(query_message.tid, kwargs['fid'], error = query_message.status) + closeFid(kwargs['tid'], kwargs['fid'], results = results) + else: + closeFid(kwargs['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 = ' 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)) + short_name = short_name[:short_name_length].decode('UTF-16LE') + + accept_result = False + if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc + accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL) + else: + accept_result = (file_attributes & search) > 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename, file_id)) if next_offset: offset += next_offset @@ -639,7 +714,11 @@ 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 kwargs['error'] == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) if service_name not in self.connected_trees: def connectCB(connect_message, **kwargs): @@ -689,17 +768,18 @@ create_context_data = create_context_data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback) + 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: p = create_message.payload + filename = self._extractLastPathComponent(path) info = SharedFile(p.create_time, p.lastaccess_time, p.lastwrite_time, p.change_time, p.file_size, p.allocation_size, p.file_attributes, - path, path) - closeFid(create_message.tid, p.fid, info = info) + filename, filename) + closeFid(kwargs['tid'], p.fid, info = info) else: errback(OperationFailure('Failed to get attributes for %s on %s: Unable to open remote file object' % ( path, service_name ), messages_history)) @@ -724,6 +804,87 @@ 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 _getSecurity_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 = [ ] + results = [ ] + + def sendCreate(tid): + 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 | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + oplock = SMB2_OPLOCK_LEVEL_NONE, + impersonation = SEC_IMPERSONATE, + create_options = 0, + create_disp = FILE_OPEN)) + 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 = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + info_type = SMB2_INFO_SECURITY, + file_info_class = 0, # [MS-SMB2] 2.2.37, 3.2.4.12 + input_buf = '', + output_buf_len = self.max_transact_size)) + m.tid = kwargs['tid'] + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid) + messages_history.append(m) + else: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to open file or directory' % ( path, service_name ), messages_history)) + + def queryCB(query_message, **kwargs): + messages_history.append(query_message) + if query_message.status == 0: + security = SecurityDescriptor.from_bytes(query_message.payload.data) + closeFid(kwargs['tid'], kwargs['fid'], result = security) + else: + closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status) + + def closeFid(tid, fid, result = 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, result = result, error = error) + messages_history.append(m) + + def closeCB(close_message, **kwargs): + if kwargs['result'] is not None: + callback(kwargs['result']) + elif kwargs['error'] is not None: + errback(OperationFailure('Failed to get the security descriptor of %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history)) + + if service_name not in self.connected_trees: + 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 the security descriptor 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) @@ -760,7 +921,7 @@ 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, + share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, oplock = SMB2_OPLOCK_LEVEL_NONE, impersonation = SEC_IMPERSONATE, create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE, @@ -781,13 +942,15 @@ file_info_class = 0x16, # FileStreamInformation [MS-FSCC] 2.4 input_buf = b'', output_buf_len = 4096)) - m.tid = create_message.tid + m.tid = kwargs['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) + tid = kwargs['tid'], + 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)) + errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history)) def infoCB(info_message, **kwargs): messages_history.append(info_message) @@ -802,9 +965,9 @@ 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)) + sendRead(kwargs['tid'], kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes']) + else: + errback(OperationFailure('Failed to retrieve %s on %s: Unable to retrieve information on file' % ( path, service_name ), messages_history)) def sendRead(tid, fid, offset, remaining_len, read_len, file_attributes): read_count = min(self.max_read_size, remaining_len) @@ -812,7 +975,7 @@ m.tid = tid self._sendSMBMessage(m) self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, - fid = fid, offset = offset, + tid = tid, fid = fid, offset = offset, remaining_len = remaining_len, read_len = read_len, file_attributes = file_attributes) @@ -826,12 +989,12 @@ 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 )) + sendRead(kwargs['tid'], kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes']) + else: + closeFid(kwargs['tid'], kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len )) else: messages_history.append(read_message) - closeFid(read_message.tid, kwargs['fid'], error = read_message.status) + closeFid(kwargs['tid'], kwargs['fid'], error = read_message.status) def closeFid(tid, fid, ret = None, error = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -907,10 +1070,6 @@ messages_history.append(create_message) if create_message.status == 0: sendWrite(create_message.tid, create_message.payload.fid, starting_offset) - elif create_message.status == 0x0103: # STATUS_PENDING - self.pending_requests[create_message.mid] = _PendingRequest(create_message.mid, expiry_time, - createCB, errback, - tid=kwargs['tid']) else: errback(OperationFailure('Failed to store %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -922,17 +1081,17 @@ 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) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, tid = tid, fid = fid, offset = offset+data_len) else: closeFid(tid, fid, offset = offset) def writeCB(write_message, **kwargs): # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history. if write_message.status == 0: - sendWrite(write_message.tid, kwargs['fid'], kwargs['offset']) + sendWrite(kwargs['tid'], kwargs['fid'], kwargs['offset']) else: messages_history.append(write_message) - closeFid(write_message.tid, kwargs['fid']) + closeFid(kwargs['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): @@ -965,7 +1124,171 @@ sendCreate(self.connected_trees[service_name]) - def _deleteFiles_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB2(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): + if not self.has_authenticated: + raise NotReadyError('SMB connection not authenticated') + + expiry_time = time.time() + timeout + pattern = None + path = path_file_pattern.replace('/', '\\') + if path.startswith('\\'): + path = path[1:] + if path.endswith('\\'): + path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB2__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB2(service_name, p, deleteCB, errback, timeout) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + if service_name not in self.connected_trees: + def connectCB(connect_message, **kwargs): + messages_history.append(connect_message) + if connect_message.status == 0: + self.connected_trees[service_name] = connect_message.tid + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB2__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB2__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB2(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB2(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + def _deleteFiles_SMB2__del(self, service_name, tid, path, callback, errback, timeout = 30): + messages_history = [ ] + + def sendCreate(tid): + create_context_data = binascii.unhexlify(b""" + 28 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00 + 44 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00 + 00 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00 + 00 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00 + 51 46 69 64 00 00 00 00 + """.replace(b' ', b'').replace(b'\n', b'')) + 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): + open_message.tid = kwargs['tid'] + messages_history.append(open_message) + if open_message.status == 0: + sendDelete(open_message.tid, open_message.payload.fid) + elif open_message.status == 0xc0000034: # STATUS_OBJECT_NAME_NOT_FOUND + callback(path) + elif open_message.status == 0xC00000BA: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history)) + + def sendDelete(tid, fid): + m = SMB2Message(SMB2SetInfoRequest(fid, + additional_info = 0, + info_type = SMB2_INFO_FILE, + file_info_class = 0x0d, # SMB2_FILE_DISPOSITION_INFO + data = b'\x01')) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4.11 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, fid = fid) + messages_history.append(m) + + def deleteCB(delete_message, **kwargs): + messages_history.append(delete_message) + if delete_message.status == 0: + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) + + def closeFid(tid, fid, status = None): + m = 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) + else: + errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) + + sendCreate(tid) + + def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, file_attributes = ATTR_NORMAL, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') @@ -986,105 +1309,6 @@ 00 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00 51 46 69 64 00 00 00 00 """.replace(b' ', b'').replace(b'\n', b'')) - 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): - open_message.tid = kwargs['tid'] - messages_history.append(open_message) - if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) - elif open_message.status == 0x0103: # STATUS_PENDING - self.pending_requests[open_message.mid] = _PendingRequest(open_message.mid, expiry_time, - createCB, errback, - tid=kwargs['tid']) - else: - errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history)) - - def sendDelete(tid, fid): - m = SMB2Message(SMB2SetInfoRequest(fid, - additional_info = 0, - info_type = SMB2_INFO_FILE, - file_info_class = 0x0d, # SMB2_FILE_DISPOSITION_INFO - data = b'\x01')) - ''' - 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 service_name not in self.connected_trees: - 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, @@ -1103,7 +1327,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendReset(open_message.tid, open_message.payload.fid) + sendReset(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to open file' % ( path, service_name ), messages_history)) @@ -1112,25 +1336,19 @@ 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) + data = struct.pack('qqqqii', 0, 0, 0, 0, file_attributes, 0))) + # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4, [MS-FSCC]: 2.4.7, [MS-FSCC]: 2.6 + m.tid = tid + self._sendSMBMessage(m) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = reset_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1160,8 +1378,6 @@ 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: @@ -1195,13 +1411,13 @@ 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) + 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: - closeFid(create_message.tid, create_message.payload.fid) + closeFid(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history)) @@ -1269,7 +1485,7 @@ def createCB(open_message, **kwargs): messages_history.append(open_message) if open_message.status == 0: - sendDelete(open_message.tid, open_message.payload.fid) + sendDelete(kwargs['tid'], open_message.payload.fid) else: errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( path, service_name ), messages_history)) @@ -1281,15 +1497,15 @@ data = b'\x01')) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1365,7 +1581,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendRename(create_message.tid, create_message.payload.fid) + sendRename(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to rename %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1378,15 +1594,15 @@ data = data)) m.tid = tid self._sendSMBMessage(m) - self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, fid = fid) + self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, tid = tid, 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) + closeFid(kwargs['tid'], kwargs['fid'], status = 0) + else: + closeFid(kwargs['tid'], kwargs['fid'], status = rename_message.status) def closeFid(tid, fid, status = None): m = SMB2Message(SMB2CloseRequest(fid)) @@ -1453,7 +1669,7 @@ def createCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: - sendEnumSnapshots(create_message.tid, create_message.payload.fid) + sendEnumSnapshots(kwargs['tid'], create_message.payload.fid) else: errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history)) @@ -1594,9 +1810,38 @@ self._handleSessionChallenge(message, ntlm_token) except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ) as ex: raise ProtocolError(str(ex), message.raw_data, message) - elif message.status.internal_value == 0xc000006d: # STATUS_LOGON_FAILURE + elif (message.status.internal_value == 0xc000006d # STATUS_LOGON_FAILURE + or message.status.internal_value == 0xc0000064 # STATUS_NO_SUCH_USER + or message.status.internal_value == 0xc000006a): # STATUS_WRONG_PASSWORD self.has_authenticated = False - self.log.info('Authentication (with extended security) failed. Please check username and password. You may need to enable/disable NTLMv2 authentication.') + self.log.info('Authentication (with extended security) failed. Please check username and password.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc0000193 # STATUS_ACCOUNT_EXPIRED + or message.status.internal_value == 0xC0000071): # STATUS_PASSWORD_EXPIRED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account or password has expired.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been locked due to too many invalid logon attempts.') + self.onAuthFailed() + elif message.status.internal_value == 0xc0000072: # STATUS_ACCOUNT_DISABLED + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Account has been disabled.') + self.onAuthFailed() + elif (message.status.internal_value == 0xc000006f # STATUS_INVALID_LOGON_HOURS + or message.status.internal_value == 0xc000015b # STATUS_LOGON_TYPE_NOT_GRANTED + or message.status.internal_value == 0xc0000070): # STATUS_INVALID_WORKSTATION + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Not allowed.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018c: # STATUS_TRUSTED_DOMAIN_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Domain not trusted.') + self.onAuthFailed() + elif message.status.internal_value == 0xc000018d: # STATUS_TRUSTED_RELATIONSHIP_FAILURE + self.has_authenticated = False + self.log.info('Authentication (with extended security) failed. Workstation not trusted.') self.onAuthFailed() else: raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status.internal_value, @@ -1658,12 +1903,13 @@ 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) + ntlm_data, signing_session_key = ntlm.generateAuthenticateMessage(server_flags, + nt_challenge_response, + lm_challenge_response, + session_key, + self.username, + self.domain, + self.my_name) if self.log.isEnabledFor(logging.DEBUG): self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response)) @@ -1683,7 +1929,7 @@ if self.is_signing_active: self.log.info("SMB signing activated. All SMB messages will be signed.") - self.signing_session_key = session_key + self.signing_session_key = signing_session_key if self.capabilities & CAP_EXTENDED_SECURITY: self.signing_challenge_response = None else: @@ -1857,13 +2103,12 @@ 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 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]) + sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) + else: + decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:]) else: closeFid(read_message.tid, kwargs['fid']) errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history)) @@ -1902,15 +2147,15 @@ setup_bytes = struct.pack(' 0 + if accept_result: + results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), + convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time), + file_size, alloc_size, file_attributes, short_name, filename)) if next_offset: offset += next_offset @@ -1988,11 +2240,15 @@ 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): + sendFindNext(find_message.tid, sid, 0, results[-1].filename, kwargs.get('support_dfs', False)) + else: + if find_message.status.internal_value == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files + callback([ ]) + else: + errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history)) + + def sendFindNext(tid, sid, resume_key, resume_file, support_dfs): setup_bytes = struct.pack(' 0: @@ -2318,11 +2575,99 @@ else: sendOpen(self.connected_trees[service_name]) - def _deleteFiles_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30): + def _deleteFiles_SMB1(self, service_name, path_file_pattern, delete_matching_folders, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') + expiry_time = time.time() + timeout + pattern = None path = path_file_pattern.replace('/', '\\') + if path.startswith('\\'): + path = path[1:] + if path.endswith('\\'): + path = path[:-1] + else: + path_components = path.split('\\') + if path_components[-1].find('*') > -1 or path_components[-1].find('?') > -1: + path = '\\'.join(path_components[:-1]) + pattern = path_components[-1] + messages_history, files_queue = [ ], [ ] + + if pattern is None: + path_components = path.split('\\') + if len(path_components) > 1: + files_queue.append(( '\\'.join(path_components[:-1]), path_components[-1] )) + else: + files_queue.append(( '', path )) + + def deleteCB(path): + if files_queue: + p, filename = files_queue.pop(0) + if filename: + if p: + filename = p + '\\' + filename + self._deleteFiles_SMB1__del(service_name, self.connected_trees[service_name], filename, deleteCB, errback, timeout) + else: + self._deleteDirectory_SMB1(service_name, p, deleteCB, errback, timeout = 30) + else: + callback(path_file_pattern) + + def listCB(files_list): + files_queue.extend(files_list) + deleteCB(None) + + if service_name not in self.connected_trees: + 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 files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + else: + errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) + + 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: + if files_queue: + deleteCB(None) + else: + self._deleteFiles_SMB1__list(service_name, path, pattern, delete_matching_folders, listCB, errback, timeout) + + def _deleteFiles_SMB1__list(self, service_name, path, pattern, delete_matching_folders, callback, errback, timeout = 30): + folder_queue = [ ] + files_list = [ ] + current_path = [ path ] + search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL + + def listCB(results): + files = [ ] + for f in filter(lambda x: x.filename not in [ '.', '..' ], results): + if f.isDirectory: + if delete_matching_folders: + folder_queue.append(current_path[0]+'\\'+f.filename) + else: + files.append(( current_path[0], f.filename )) + if current_path[0]!=path and delete_matching_folders: + files.append(( current_path[0], None )) + + if files: + files_list[0:0] = files + + if folder_queue: + p = folder_queue.pop() + current_path[0] = p + self._listPath_SMB1(service_name, current_path[0], listCB, errback, search = search, pattern = '*', timeout = 30) + else: + callback(files_list) + + self._listPath_SMB1(service_name, path, listCB, errback, search = search, pattern = pattern, timeout = timeout) + + def _deleteFiles_SMB1__del(self, service_name, tid, path, callback, errback, timeout = 30): messages_history = [ ] def sendDelete(tid): @@ -2336,9 +2681,79 @@ def deleteCB(delete_message, **kwargs): messages_history.append(delete_message) if not delete_message.status.hasError: + callback(path) + elif delete_message.status.internal_value == 0xC000000F: # [MS-ERREF]: STATUS_NO_SUCH_FILE + # If there are no matching files, we just treat as success instead of failing callback(path_file_pattern) - else: - errback(OperationFailure('Failed to store %s on %s: Delete failed' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC00000BA: # [MS-ERREF]: STATUS_FILE_IS_A_DIRECTORY + errback(OperationFailure('Failed to delete %s on %s: Cannot delete a folder. Please use deleteDirectory() method or append "/*" to your path if you wish to delete all files in the folder.' % ( path, service_name ), messages_history)) + elif delete_message.status.internal_value == 0xC0000034: # [MS-ERREF]: STATUS_OBJECT_NAME_INVALID + errback(OperationFailure('Failed to delete %s on %s: Path not found' % ( path, service_name ), messages_history)) + else: + errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history)) + + sendDelete(tid) + + def _resetFileAttributes_SMB1(self, service_name, path_file_pattern, callback, errback, file_attributes=ATTR_NORMAL, timeout = 30): + raise NotReadyError('resetFileAttributes is not yet implemented for SMB1') + + def _createDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30): + 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 service_name not in self.connected_trees: + 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 service_name not in self.connected_trees: def connectCB(connect_message, **kwargs): @@ -2347,84 +2762,7 @@ 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 service_name not in self.connected_trees: - 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 service_name not in self.connected_trees: - 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)) + errback(OperationFailure('Failed to delete 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) @@ -2559,6 +2897,9 @@ def _echo_SMB1(self, data, callback, errback, timeout = 30): messages_history = [ ] + if not isinstance(data, type(b'')): + raise TypeError('Echo data must be %s not %s' % (type(b'').__name__, type(data).__name__)) + def echoCB(echo_message, **kwargs): messages_history.append(echo_message) if not echo_message.status.hasError: @@ -2571,10 +2912,18 @@ self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback) messages_history.append(m) + def _extractLastPathComponent(self, path): + return path.replace('\\', '/').split('/')[-1] + class SharedDevice: """ Contains information about a single shared device on the remote server. + + The following attributes are available: + + * name : An unicode string containing the name of the shared device + * comments : An unicode string containing the user description of the shared device """ # The following constants are taken from [MS-SRVS]: 2.2.2.4 @@ -2629,19 +2978,33 @@ 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). + one of these prohibited characters: "\\/[]:+|<>=;?,* (see [MS-CIFS]: 2.2.1.1.1 for more details). + + The following attributes are available: + + * create_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server + * last_access_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server + * last_write_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server + * last_attr_change_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server + * file_size : File size in number of bytes + * alloc_size : Total number of bytes allocated to store this file + * file_attributes : A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. + * short_name : Unicode string containing the short name of this file (usually in 8.3 notation) + * filename : Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + * file_id : Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 """ - def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename): + def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename, file_id=None): self.create_time = create_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server self.last_access_time = last_access_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server self.last_write_time = last_write_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server self.last_attr_change_time = last_attr_change_time #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server self.file_size = file_size #: File size in number of bytes self.alloc_size = alloc_size #: Total number of bytes allocated to store this file - self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3 + self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py. self.short_name = short_name #: Unicode string containing the short name of this file (usually in 8.3 notation) self.filename = filename #: Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters. + self.file_id = file_id #: Long value representing the file reference number for the file. If the remote system does not support this field, this field will be None or 0. See [MS-FSCC]: 2.4.17 @property def isDirectory(self): @@ -2652,6 +3015,16 @@ def isReadOnly(self): """A convenient property to return True if this file resource is read-only on the remote server""" return bool(self.file_attributes & ATTR_READONLY) + + @property + def isNormal(self): + """ + A convenient property to return True if this is a normal file. + + Note that pysmb defines a normal file as a file entry that is not read-only, not hidden, not system, not archive and not a directory. + It ignores other attributes like compression, indexed, sparse, temporary and encryption. + """ + return (self.file_attributes==ATTR_NORMAL) or ((self.file_attributes & 0xff)==0) def __unicode__(self): return 'Shared file: %s (FileSize:%d bytes, isDirectory:%s)' % ( self.filename, self.file_size, self.isDirectory ) diff --git a/python3/smb/ntlm.py b/python3/smb/ntlm.py index bc5c783..d364f98 100644 --- a/python3/smb/ntlm.py +++ b/python3/smb/ntlm.py @@ -1,5 +1,6 @@ -import types, hmac, binascii, struct, random +import types, hmac, binascii, struct, random, string +from .utils.rc4 import RC4_encrypt from .utils.pyDes import des try: @@ -58,14 +59,14 @@ NTLM_FLAGS = NTLM_NegotiateUnicode | \ NTLM_RequestTarget | \ + NTLM_NegotiateSign | \ NTLM_NegotiateNTLM | \ NTLM_NegotiateAlwaysSign | \ NTLM_NegotiateExtendedSecurity | \ NTLM_NegotiateTargetInfo | \ NTLM_NegotiateVersion | \ NTLM_Negotiate128 | \ - NTLM_NegotiateKeyExchange | \ - NTLM_Negotiate56 + NTLM_NegotiateKeyExchange def generateNegotiateMessage(): """ @@ -81,7 +82,7 @@ return s -def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'): +def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, request_session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'): """ References: =========== @@ -89,6 +90,13 @@ """ FORMAT = '<8sIHHIHHIHHIHHIHHIHHII' FORMAT_SIZE = struct.calcsize(FORMAT) + + # [MS-NLMP]: 3.1.5.1.2 + # http://grutz.jingojango.net/exploits/davenport-ntlm.html + session_key = session_signing_key = request_session_key + if challenge_flags & NTLM_NegotiateKeyExchange: + session_signing_key = "".join([ random.choice(string.digits+string.ascii_letters) for _ in range(16) ]).encode('ascii') + session_key = RC4_encrypt(request_session_key, session_signing_key) lm_response_length = len(lm_response) lm_response_offset = FORMAT_SIZE @@ -125,7 +133,7 @@ session_key_length, session_key_length, session_key_offset, auth_flags) - return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key + return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key, session_signing_key def decodeChallengeMessage(ntlm_data): @@ -161,13 +169,13 @@ d = MD4() d.update(password.encode('UTF-16LE')) ntlm_hash = d.digest() # The NT password hash - response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions + response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE'), 'md5').digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions temp = b'\x01\x01' + b'\0'*6 + client_timestamp + client_challenge + b'\0'*4 + server_info - ntproofstr = hmac.new(response_key, server_challenge + temp).digest() + ntproofstr = hmac.new(response_key, server_challenge + temp, 'md5').digest() nt_challenge_response = ntproofstr + temp - lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge - session_key = hmac.new(response_key, ntproofstr).digest() + lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge, 'md5').digest() + client_challenge + session_key = hmac.new(response_key, ntproofstr, 'md5').digest() return nt_challenge_response, lm_challenge_response, session_key diff --git a/python3/smb/security_descriptors.py b/python3/smb/security_descriptors.py new file mode 100644 index 0000000..44a678f --- /dev/null +++ b/python3/smb/security_descriptors.py @@ -0,0 +1,367 @@ +""" +This module implements security descriptors, and the partial structures +used in them, as specified in [MS-DTYP]. +""" + +import struct + + +# Security descriptor control flags +# [MS-DTYP]: 2.4.6 +SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001 +SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002 +SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004 +SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008 +SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010 +SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020 +SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040 +SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080 +SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100 +SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200 +SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400 +SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800 +SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000 +SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000 +SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000 +SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000 + +# ACE types +# [MS-DTYP]: 2.4.4.1 +ACE_TYPE_ACCESS_ALLOWED = 0x00 +ACE_TYPE_ACCESS_DENIED = 0x01 +ACE_TYPE_SYSTEM_AUDIT = 0x02 +ACE_TYPE_SYSTEM_ALARM = 0x03 +ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04 +ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05 +ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06 +ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07 +ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08 +ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09 +ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A +ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B +ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C +ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D +ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E +ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F +ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10 +ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11 +ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12 +ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13 + +# ACE flags +# [MS-DTYP]: 2.4.4.1 +ACE_FLAG_OBJECT_INHERIT = 0x01 +ACE_FLAG_CONTAINER_INHERIT = 0x02 +ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04 +ACE_FLAG_INHERIT_ONLY = 0x08 +ACE_FLAG_INHERITED = 0x10 +ACE_FLAG_SUCCESSFUL_ACCESS = 0x40 +ACE_FLAG_FAILED_ACCESS = 0x80 + +# Pre-defined well-known SIDs +# [MS-DTYP]: 2.4.2.4 +SID_NULL = "S-1-0-0" +SID_EVERYONE = "S-1-1-0" +SID_LOCAL = "S-1-2-0" +SID_CONSOLE_LOGON = "S-1-2-1" +SID_CREATOR_OWNER = "S-1-3-0" +SID_CREATOR_GROUP = "S-1-3-1" +SID_OWNER_SERVER = "S-1-3-2" +SID_GROUP_SERVER = "S-1-3-3" +SID_OWNER_RIGHTS = "S-1-3-4" +SID_NT_AUTHORITY = "S-1-5" +SID_DIALUP = "S-1-5-1" +SID_NETWORK = "S-1-5-2" +SID_BATCH = "S-1-5-3" +SID_INTERACTIVE = "S-1-5-4" +SID_SERVICE = "S-1-5-6" +SID_ANONYMOUS = "S-1-5-7" +SID_PROXY = "S-1-5-8" +SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9" +SID_PRINCIPAL_SELF = "S-1-5-10" +SID_AUTHENTICATED_USERS = "S-1-5-11" +SID_RESTRICTED_CODE = "S-1-5-12" +SID_TERMINAL_SERVER_USER = "S-1-5-13" +SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14" +SID_THIS_ORGANIZATION = "S-1-5-15" +SID_IUSR = "S-1-5-17" +SID_LOCAL_SYSTEM = "S-1-5-18" +SID_LOCAL_SERVICE = "S-1-5-19" +SID_NETWORK_SERVICE = "S-1-5-20" +SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496" +SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497" +SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544" +SID_BUILTIN_USERS = "S-1-5-32-545" +SID_BUILTIN_GUESTS = "S-1-5-32-546" +SID_POWER_USERS = "S-1-5-32-547" +SID_ACCOUNT_OPERATORS = "S-1-5-32-548" +SID_SERVER_OPERATORS = "S-1-5-32-549" +SID_PRINTER_OPERATORS = "S-1-5-32-550" +SID_BACKUP_OPERATORS = "S-1-5-32-551" +SID_REPLICATOR = "S-1-5-32-552" +SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554" +SID_REMOTE_DESKTOP = "S-1-5-32-555" +SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556" +SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557" +SID_PERFMON_USERS = "S-1-5-32-558" +SID_PERFLOG_USERS = "S-1-5-32-559" +SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560" +SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561" +SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562" +SID_IIS_IUSRS = "S-1-5-32-568" +SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569" +SID_EVENT_LOG_READERS = "S-1-5-32-573" +SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574" +SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575" +SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576" +SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577" +SID_HYPER_V_ADMINS = "S-1-5-32-578" +SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579" +SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580" +SID_WRITE_RESTRICTED_CODE = "S-1-5-33" +SID_NTLM_AUTHENTICATION = "S-1-5-64-10" +SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14" +SID_DIGEST_AUTHENTICATION = "S-1-5-64-21" +SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1" +SID_NT_SERVICE = "S-1-5-80" +SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0" +SID_LOCAL_ACCOUNT = "S-1-5-113" +SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114" +SID_OTHER_ORGANIZATION = "S-1-5-1000" +SID_ALL_APP_PACKAGES = "S-1-15-2-1" +SID_ML_UNTRUSTED = "S-1-16-0" +SID_ML_LOW = "S-1-16-4096" +SID_ML_MEDIUM = "S-1-16-8192" +SID_ML_MEDIUM_PLUS = "S-1-16-8448" +SID_ML_HIGH = "S-1-16-12288" +SID_ML_SYSTEM = "S-1-16-16384" +SID_ML_PROTECTED_PROCESS = "S-1-16-20480" +SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1" +SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2" +SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3" +SID_KEY_TRUST_IDENTITY = "S-1-18-4" +SID_KEY_PROPERTY_MFA = "S-1-18-5" +SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6" + + +class SID(object): + """ + A Windows security identifier. Represents a single principal, such a + user or a group, as a sequence of numbers consisting of the revision, + identifier authority, and a variable-length list of subauthorities. + + See [MS-DTYP]: 2.4.2 + """ + def __init__(self, revision, identifier_authority, subauthorities): + #: Revision, should always be 1. + self.revision = revision + #: An integer representing the identifier authority. + self.identifier_authority = identifier_authority + #: A list of integers representing all subauthorities. + self.subauthorities = subauthorities + + def __str__(self): + """ + String representation, as specified in [MS-DTYP]: 2.4.2.1 + """ + if self.identifier_authority >= 2**32: + id_auth = '%#x' % (self.identifier_authority,) + else: + id_auth = self.identifier_authority + auths = [self.revision, id_auth] + self.subauthorities + return 'S-' + '-'.join(str(subauth) for subauth in auths) + + def __repr__(self): + return 'SID(%r)' % (str(self),) + + @classmethod + def from_bytes(cls, data, return_tail=False): + revision, subauth_count = struct.unpack('Q', b'\x00\x00' + data[2:8])[0] + subauth_data = data[8:] + subauthorities = [struct.unpack('= size + + body = data[header_size:size] + additional_data = {} + + # In all ACE types, the mask immediately follows the header. + mask = struct.unpack('= size + + for i in range(count): + ace_size = struct.unpack(' 0 # SMB2_SESSION_FLAG_IS_GUEST + + @property + def isAnonymousSession(self): + return (self.session_flags & 0x0002) > 0 # SMB2_SESSION_FLAG_IS_NULL + def decode(self, message): assert message.command == SMB2_COM_SESSION_SETUP @@ -362,6 +370,7 @@ def prepare(self, message): buf = self.filename.encode('UTF-16LE') + filename_len = len(buf) if self.create_context_data: n = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf) if n % 8 != 0: @@ -389,7 +398,7 @@ self.create_disp, self.create_options, SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # NameOffset - len(self.filename)*2, # NameLength in bytes + filename_len, # Length of encoded filename in bytes create_context_offset, # CreateContextOffset len(self.create_context_data) # CreateContextLength ) + buf diff --git a/python3/smb/smb_constants.py b/python3/smb/smb_constants.py index 685ae68..de85652 100644 --- a/python3/smb/smb_constants.py +++ b/python3/smb/smb_constants.py @@ -115,6 +115,7 @@ FILE_READ_EA = 0x08 FILE_WRITE_EA = 0x10 FILE_EXECUTE = 0x20 +FILE_DELETE_CHILD = 0x40 FILE_READ_ATTRIBUTES = 0x80 FILE_WRITE_ATTRIBUTES = 0x0100 DELETE = 0x010000 @@ -225,9 +226,13 @@ SMB_FILE_ATTRIBUTE_READONLY = 0x01 SMB_FILE_ATTRIBUTE_HIDDEN = 0x02 SMB_FILE_ATTRIBUTE_SYSTEM = 0x04 -SMB_FILE_ATTRIBUTE_VOLUME = 0x08 +SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10 SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20 +# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files +# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs. +SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000 +# Do not use the following values for listPath() operations as they are not supported for SMB2 SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100 SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200 SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400 @@ -237,3 +242,16 @@ # Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001 SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002 + +# Bitmask for security information fields, specified as +# AdditionalInformation in SMB2 +# [MS-SMB]: 2.2.7.4 +# [MS-SMB2]: 2.2.37 +OWNER_SECURITY_INFORMATION = 0x00000001 +GROUP_SECURITY_INFORMATION = 0x00000002 +DACL_SECURITY_INFORMATION = 0x00000004 +SACL_SECURITY_INFORMATION = 0x00000008 +LABEL_SECURITY_INFORMATION = 0x00000010 +ATTRIBUTE_SECURITY_INFORMATION = 0x00000020 +SCOPE_SECURITY_INFORMATION = 0x00000040 +BACKUP_SECURITY_INFORMATION = 0x00010000 diff --git a/python3/smb/smb_structs.py b/python3/smb/smb_structs.py index 3583605..2332014 100644 --- a/python3/smb/smb_structs.py +++ b/python3/smb/smb_structs.py @@ -142,6 +142,10 @@ self.parameters_data = b'' self.data = b'' self.payload = None + + @property + def isAsync(self): + return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND) @property def isReply(self): @@ -1279,7 +1283,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 diff --git a/python3/smb/utils/md4.py b/python3/smb/utils/md4.py index f4a7ad0..4948904 100644 --- a/python3/smb/utils/md4.py +++ b/python3/smb/utils/md4.py @@ -82,16 +82,18 @@ dest.C = self.C dest.D = self.D dest.count = self.count - for i in range(self.count): + for i in range(int(self.count)): dest.buf[i] = self.buf[i] return dest #----------------------------------------------------- def update(self, str): - - buf = [] - for i in str: buf.append(ord(i)) + if isinstance(str, bytes): + buf = list(str) + else: + buf = [ord(i) for i in str] + ilen = U32(len(buf)) # check if the first length is out of range @@ -227,7 +229,7 @@ res[14]=(temp.D >> 16) & U32(0xFF) res[15]=(temp.D >> 24) & U32(0xFF) - return int_array2str(res) + return int_array2str(res).encode('UTF-16LE') #==================================================================== # helpers diff --git a/python3/smb/utils/rc4.py b/python3/smb/utils/rc4.py new file mode 100644 index 0000000..037c1d3 --- /dev/null +++ b/python3/smb/utils/rc4.py @@ -0,0 +1,22 @@ + +def RC4_encrypt(key, data): + S = list(range(256)) + j = 0 + + key_len = len(key) + for i in list(range(256)): + j = (j + S[i] + key[i % key_len]) % 256 + S[i], S[j] = S[j], S[i] + + j = 0 + y = 0 + out = [] + + for char in data: + j = (j + 1) % 256 + y = (y + S[j]) % 256 + S[j], S[y] = S[y], S[j] + + out.append(char ^ S[(S[j] + S[y]) % 256]) + + return bytes(out) diff --git a/python3/smb/utils/sha256.py b/python3/smb/utils/sha256.py index d535a4b..574a639 100644 --- a/python3/smb/utils/sha256.py +++ b/python3/smb/utils/sha256.py @@ -1,4 +1,3 @@ -#!/usr/bin/python __author__ = 'Thomas Dixon' __license__ = 'MIT' diff --git a/python3/tests/DirectSMBConnectionTests/test_auth.py b/python3/tests/DirectSMBConnectionTests/test_auth.py index c3f1081..f789944 100644 --- a/python3/tests/DirectSMBConnectionTests/test_auth.py +++ b/python3/tests/DirectSMBConnectionTests/test_auth.py @@ -1,43 +1,86 @@ +from nose2.tools.decorators import with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs -conn = None +conn, conn2, conn3 = None, None, None def teardown_func(): - global conn - conn.close() + global conn, conn2, conn3 + if conn: + conn.close() + if conn2: + conn2.close() + if conn3: + conn3.close(); -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_NTLMv1_auth_SMB1(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) -def test_NTLMv2_auth_SMB1(): - global conn + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) + +@with_teardown(teardown_func) +def test_NTLMv1_auth_SMB1_callable_password(): + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], lambda: info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) + conn2 = SMBConnection(info['user'], lambda: 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', lambda: 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) + +@with_teardown(teardown_func) +def test_NTLMv2_auth_SMB1(): + global conn, conn2, conn3 + smb_structs.SUPPORT_SMB2 = False + info = getConnectionInfo() + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) + +@with_teardown(teardown_func) def test_NTLMv1_auth_SMB2(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = False, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) + +@with_teardown(teardown_func) def test_NTLMv2_auth_SMB2(): - global conn + global conn, conn2, conn3 smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) + + conn2 = SMBConnection(info['user'], 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn2.connect(info['server_ip'], info['server_port']) + + conn3 = SMBConnection('INVALIDUSER', 'wrongPass', info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) + assert not conn3.connect(info['server_ip'], info['server_port']) diff --git a/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py b/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py index 814d89d..dbbc313 100644 --- a/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py +++ b/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import os, time, random +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -13,7 +13,7 @@ smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): @@ -21,14 +21,15 @@ smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_english_directory_SMB1(): global conn @@ -45,7 +46,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_english_directory_SMB2(): global conn @@ -62,7 +64,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_unicode_directory_SMB1(): global conn @@ -79,7 +82,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_unicode_directory_SMB2(): global conn diff --git a/python3/tests/DirectSMBConnectionTests/test_echo.py b/python3/tests/DirectSMBConnectionTests/test_echo.py index c81675d..9633e04 100644 --- a/python3/tests/DirectSMBConnectionTests/test_echo.py +++ b/python3/tests/DirectSMBConnectionTests/test_echo.py @@ -1,22 +1,23 @@ import random +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection from .util import getConnectionInfo -from nose.tools import with_setup conn = None def setup_func(): global conn info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func, teardown_func) +@with_setup(setup_func) +@with_teardown(teardown_func) def test_echo(): global conn diff --git a/python3/tests/DirectSMBConnectionTests/test_listpath.py b/python3/tests/DirectSMBConnectionTests/test_listpath.py index c3b08a7..2dfbd3a 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listpath.py +++ b/python3/tests/DirectSMBConnectionTests/test_listpath.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb.smb_constants import * +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -11,21 +12,22 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listPath_SMB1(): global conn results = conn.listPath('smbtest', '/') @@ -36,7 +38,8 @@ assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names assert ( 'rfc1001.txt', False ) in filenames # Test short English file names -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listSubPath_SMB1(): global conn results = conn.listPath('smbtest', '/Test Folder with Long Name/') @@ -45,7 +48,16 @@ assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', True ) in filenames -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathWithManyFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames))==999 + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listPath_SMB2(): global conn results = conn.listPath('smbtest', '/') @@ -56,7 +68,8 @@ assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names assert ( 'rfc1001.txt', False ) in filenames # Test short English file names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listSubPath_SMB2(): global conn results = conn.listPath('smbtest', '/Test Folder with Long Name/') @@ -64,3 +77,96 @@ assert ( 'Test File.txt', False ) in filenames assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathWithManyFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames))==999 + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterForDirectory_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterForDirectory_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterForFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterForFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert not isDirectory + + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterPattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterPattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterUnicodePattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterUnicodePattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames diff --git a/python3/tests/DirectSMBConnectionTests/test_listshares.py b/python3/tests/DirectSMBConnectionTests/test_listshares.py index 23328ec..2c9e815 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listshares.py +++ b/python3/tests/DirectSMBConnectionTests/test_listshares.py @@ -1,8 +1,8 @@ +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -10,27 +10,29 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listshares_SMB1(): global conn results = conn.listShares() assert 'smbtest' in [r.name.lower() for r in results] -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listshares_SMB2(): global conn results = conn.listShares() diff --git a/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py b/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py index 551581f..e48c85f 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py +++ b/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py @@ -1,8 +1,8 @@ +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -10,27 +10,29 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listsnapshots_SMB1(): global conn results = conn.listSnapshots('smbtest', '/rfc1001.txt') assert len(results) > 0 -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listsnapshots_SMB2(): global conn results = conn.listSnapshots('smbtest', '/rfc1001.txt') diff --git a/python3/tests/DirectSMBConnectionTests/test_rename.py b/python3/tests/DirectSMBConnectionTests/test_rename.py index cb6d880..b67b3b2 100644 --- a/python3/tests/DirectSMBConnectionTests/test_rename.py +++ b/python3/tests/DirectSMBConnectionTests/test_rename.py @@ -2,10 +2,10 @@ import os, time, random from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -13,21 +13,22 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_english_file_SMB1(): global conn @@ -50,7 +51,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_english_file_SMB2(): global conn @@ -73,7 +75,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_unicode_file_SMB1(): global conn @@ -96,7 +99,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_unicode_file_SMB2(): global conn @@ -119,7 +123,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_english_directory_SMB1(): global conn @@ -142,7 +147,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_english_directory_SMB2(): global conn @@ -165,7 +171,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_unicode_directory_SMB1(): global conn @@ -188,7 +195,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_unicode_directory_SMB2(): global conn diff --git a/python3/tests/DirectSMBConnectionTests/test_retrievefile.py b/python3/tests/DirectSMBConnectionTests/test_retrievefile.py index e49ab8f..7a93733 100644 --- a/python3/tests/DirectSMBConnectionTests/test_retrievefile.py +++ b/python3/tests/DirectSMBConnectionTests/test_retrievefile.py @@ -2,10 +2,10 @@ import os, tempfile from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs try: import hashlib @@ -20,21 +20,22 @@ global conn smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_multiplereads_SMB1(): # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) global conn @@ -48,7 +49,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_multiplereads_SMB2(): # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) global conn @@ -62,7 +64,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_longfilename_SMB1(): # Test file retrieval that has a long English filename global conn @@ -76,7 +79,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_longfilename_SMB2(): # Test file retrieval that has a long English filename global conn @@ -90,7 +94,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_unicodefilename_SMB1(): # Test file retrieval that has a long non-English filename inside a folder with a non-English name global conn @@ -104,7 +109,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_unicodefilename_SMB2(): # Test file retrieval that has a long non-English filename inside a folder with a non-English name global conn @@ -118,7 +124,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_SMB1(): # Test file retrieval from offset to EOF global conn @@ -132,7 +139,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_SMB2(): # Test file retrieval from offset to EOF global conn @@ -146,7 +154,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_biglimit_SMB1(): # Test file retrieval from offset with a big max_length global conn @@ -160,7 +169,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_biglimit_SMB2(): # Test file retrieval from offset with a big max_length global conn @@ -174,7 +184,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_smalllimit_SMB1(): # Test file retrieval from offset with a small max_length global conn @@ -188,7 +199,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_smalllimit_SMB2(): # Test file retrieval from offset with a small max_length global conn @@ -202,7 +214,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_zerolimit_SMB1(): # Test file retrieval from offset to EOF with max_length=0 global conn @@ -216,7 +229,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_zerolimit_SMB2(): # Test file retrieval from offset to EOF with max_length=0 global conn diff --git a/python3/tests/DirectSMBConnectionTests/test_storefile.py b/python3/tests/DirectSMBConnectionTests/test_storefile.py index 80c804c..fe9144c 100644 --- a/python3/tests/DirectSMBConnectionTests/test_storefile.py +++ b/python3/tests/DirectSMBConnectionTests/test_storefile.py @@ -2,10 +2,10 @@ import os, tempfile, random, time from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs try: import hashlib @@ -25,7 +25,7 @@ smb_structs.SUPPORT_SMB2 = False info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def setup_func_SMB2(): @@ -33,7 +33,7 @@ smb_structs.SUPPORT_SMB2 = True info = getConnectionInfo() - conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) + conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True, is_direct_tcp = True) assert conn.connect(info['server_ip'], info['server_port']) def teardown_func(): @@ -41,7 +41,8 @@ conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_store_long_filename_SMB1(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -64,7 +65,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_store_long_filename_SMB2(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -87,7 +89,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_store_unicode_filename_SMB1(): filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -110,7 +113,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_store_unicode_filename_SMB2(): filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) diff --git a/python3/tests/DirectSMBTwistedTests/test_auth.py b/python3/tests/DirectSMBTwistedTests/test_auth.py deleted file mode 100644 index 967a117..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_auth.py +++ /dev/null @@ -1,77 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class AuthFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - self.d.callback(True) - - def onAuthFailed(self): - self.d.callback(False) - - -@deferred(timeout=5.0) -def test_NTLMv1_auth_SMB1(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = False - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv2_auth_SMB1(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = False - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv1_auth_SMB2(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = True - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv2_auth_SMB2(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = True - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_createdeletedirectory.py b/python3/tests/DirectSMBTwistedTests/test_createdeletedirectory.py deleted file mode 100644 index 8874da8..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_createdeletedirectory.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, random, time -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class DirectoryFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = '' - self.path = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def createDone(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.path.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - names = [e.filename for e in entries] - assert os.path.basename(self.path.replace('/', os.sep)) in names - - d = self.deleteDirectory(self.service_name, self.path) - d.addCallback(self.deleteDone) - d.addErrback(self.d.errback) - - def deleteDone(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.path.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - names = [e.filename for e in entries] - assert os.path.basename(self.path.replace('/', os.sep)) not in names - self.d.callback(True) - - def onAuthOK(self): - d = self.createDirectory(self.service_name, self.path) - d.addCallback(self.createDone) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_english_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + 'TestDir %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_english_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + 'TestDir %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_unicode_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + '文件夹创建 %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_unicode_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + '文件夹创建 %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_echo.py b/python3/tests/DirectSMBTwistedTests/test_echo.py deleted file mode 100644 index b781eee..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_echo.py +++ /dev/null @@ -1,39 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from .util import getConnectionInfo - - -class EchoFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.echo_data = 'This is an echo test' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(data): - assert data == self.echo_data - self.d.callback(True) - - d = self.echo(self.echo_data) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_echo(): - info = getConnectionInfo() - factory = EchoFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_listpath.py b/python3/tests/DirectSMBTwistedTests/test_listpath.py deleted file mode 100644 index a827955..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_listpath.py +++ /dev/null @@ -1,56 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListPathFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - filenames = [( r.filename, r.isDirectory ) for r in results] - assert ( '\u6d4b\u8bd5\u6587\u4ef6\u5939', True ) in filenames # Test non-English folder names - assert ( 'Test Folder with Long Name', True ) in filenames # Test long English folder names - assert ( 'TestDir1', True ) in filenames # Test short English folder names - assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names - assert ( 'rfc1001.txt', False ) in filenames # Test short English file names - - self.d.callback(True) - - d = self.listPath('smbtest', '/', timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listPath_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListPathFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listPath_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListPathFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_listshares.py b/python3/tests/DirectSMBTwistedTests/test_listshares.py deleted file mode 100644 index 9c35919..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_listshares.py +++ /dev/null @@ -1,51 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListSharesFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - assert 'smbtest' in [r.name.lower() for r in results] - self.d.callback(True) - self.instance.transport.loseConnection() - - d = self.listShares(timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listshares_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListSharesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listshares_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListSharesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_listsnapshots.py b/python3/tests/DirectSMBTwistedTests/test_listsnapshots.py deleted file mode 100644 index 7c3a5b6..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_listsnapshots.py +++ /dev/null @@ -1,57 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListSnapshotsFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = None - self.path = None - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - assert len(results) > 0 - self.d.callback(True) - self.instance.transport.loseConnection() - - d = self.listSnapshots(self.service_name, self.path, timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listshares_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListSnapshotsFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = '/rfc1001.txt' - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listshares_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListSnapshotsFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = '/rfc1001.txt' - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_rename.py b/python3/tests/DirectSMBTwistedTests/test_rename.py deleted file mode 100644 index 2658d79..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_rename.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, random, time -from io import StringIO -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class RenameFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service = '' - self.new_path = '' - self.old_path = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def pathCreated(self, result): - d = self.listPath(self.service, os.path.dirname(self.old_path.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.old_path.replace('/', os.sep)) in filenames - assert os.path.basename(self.new_path.replace('/', os.sep)) not in filenames - - d = self.rename(self.service, self.old_path, self.new_path) - d.addCallback(self.renameComplete) - d.addErrback(self.d.errback) - - def renameComplete(self, result): - d = self.listPath(self.service, os.path.dirname(self.new_path.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.new_path.replace('/', os.sep)) in filenames - assert os.path.basename(self.old_path.replace('/', os.sep)) not in filenames - self.cleanup() - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -class RenameFileFactory(RenameFactory): - - def onAuthOK(self): - d = self.storeFile(self.service, self.old_path, StringIO('Rename file test')) - d.addCallback(self.pathCreated) - d.addErrback(self.d.errback) - - def cleanup(self): - d = self.deleteFiles(self.service, self.new_path) - d.chainDeferred(self.d) - - -class RenameDirectoryFactory(RenameFactory): - - def onAuthOK(self): - d = self.createDirectory(self.service, self.old_path) - d.addCallback(self.pathCreated) - d.addErrback(self.d.errback) - - def cleanup(self): - d = self.deleteDirectory(self.service, self.new_path) - d.chainDeferred(self.d) - - -@deferred(timeout=30.0) -def test_rename_english_file_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_file_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_file_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_file_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_retrievefile.py b/python3/tests/DirectSMBTwistedTests/test_retrievefile.py deleted file mode 100644 index bbfc7d0..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_retrievefile.py +++ /dev/null @@ -1,278 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, tempfile -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - -try: - import hashlib - def MD5(): return hashlib.md5() -except ImportError: - import md5 - def MD5(): return md5.new() - - -class RetrieveFileFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.temp_fh = tempfile.NamedTemporaryFile(prefix = 'pysmbtest-') - self.service = '' - self.path = '' - self.digest = '' - self.offset = 0 - self.max_length = -1 - self.filesize = 0 - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def fileRetrieved(self, write_result): - file_obj, file_attributes, file_size = write_result - assert file_obj == self.temp_fh - - md = MD5() - filesize = 0 - self.temp_fh.seek(0) - while True: - s = self.temp_fh.read(8192) - if not s: - break - md.update(s) - filesize += len(s) - - assert self.filesize == filesize - assert md.hexdigest() == self.digest - - self.temp_fh.close() - self.d.callback(True) - self.instance.transport.loseConnection() - - def onAuthOK(self): - assert self.service - assert self.path - - d = self.retrieveFileFromOffset(self.service, self.path, self.temp_fh, self.offset, self.max_length, timeout = 15) - d.addCallback(self.fileRetrieved) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=30.0) -def test_retr_multiplereads_SMB1(): - # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/rfc1001.txt' - factory.digest = '5367c2bbf97f521059c78eab65309ad3' - factory.filesize = 158437 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_multiplereads_SMB2(): - # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/rfc1001.txt' - factory.digest = '5367c2bbf97f521059c78eab65309ad3' - factory.filesize = 158437 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_longfilename_SMB1(): - # Test file retrieval that has a long English filename - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/Implementing CIFS - SMB.html' - factory.digest = '671c5700d279fcbbf958c1bba3c2639e' - factory.filesize = 421269 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_longfilename_SMB2(): - # Test file retrieval that has a long English filename - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/Implementing CIFS - SMB.html' - factory.digest = '671c5700d279fcbbf958c1bba3c2639e' - factory.filesize = 421269 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_unicodefilename_SMB1(): - # Test file retrieval that has a long non-English filename inside a folder with a non-English name - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '8a44c1e80d55e91c92350955cdf83442' - factory.filesize = 256000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_unicodefilename_SMB2(): - # Test file retrieval that has a long non-English filename inside a folder with a non-English name - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '8a44c1e80d55e91c92350955cdf83442' - factory.filesize = 256000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_SMB1(): - # Test file retrieval from offset to EOF - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'a141bd8024571ce7cb5c67f2b0d8ea0b' - factory.filesize = 156000 - factory.offset = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_SMB2(): - # Test file retrieval from offset to EOF - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'a141bd8024571ce7cb5c67f2b0d8ea0b' - factory.filesize = 156000 - factory.offset = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_biglimit_SMB1(): - # Test file retrieval from offset with a big max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '83b7afd7c92cdece3975338b5ca0b1c5' - factory.filesize = 100000 - factory.offset = 100000 - factory.max_length = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_biglimit_SMB2(): - # Test file retrieval from offset with a big max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '83b7afd7c92cdece3975338b5ca0b1c5' - factory.filesize = 100000 - factory.offset = 100000 - factory.max_length = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_smalllimit_SMB1(): - # Test file retrieval from offset with a small max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '746f60a96b39b712a7b6e17ddde19986' - factory.filesize = 10 - factory.offset = 100000 - factory.max_length = 10 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_smalllimit_SMB2(): - # Test file retrieval from offset with a small max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '746f60a96b39b712a7b6e17ddde19986' - factory.filesize = 10 - factory.offset = 100000 - factory.max_length = 10 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_zerolimit_SMB1(): - # Test file retrieval from offset to EOF with max_length=0 - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'd41d8cd98f00b204e9800998ecf8427e' - factory.filesize = 0 - factory.offset = 100000 - factory.max_length = 0 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_zerolimit_SMB2(): - # Test file retrieval from offset to EOF with max_length=0 - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'd41d8cd98f00b204e9800998ecf8427e' - factory.filesize = 0 - factory.offset = 100000 - factory.max_length = 0 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/test_storefile.py b/python3/tests/DirectSMBTwistedTests/test_storefile.py deleted file mode 100644 index 0d5e6d0..0000000 --- a/python3/tests/DirectSMBTwistedTests/test_storefile.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, time, random -from io import StringIO -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - -try: - import hashlib - def MD5(): return hashlib.md5() -except ImportError: - import md5 - def MD5(): return md5.new() - -class StoreFilesFactory(SMBProtocolFactory): - """ - A super test factory that tests store file, list files, retrieve file and delete file functionlities in sequence. - """ - - TEST_FILENAME = os.path.join(os.path.dirname(__file__), os.pardir, 'SupportFiles', 'binary.dat') - TEST_FILESIZE = 256000 - TEST_DIGEST = 'bb6303f76e29f354b6fdf6ef58587e48' - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = '' - self.filename = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def storeComplete(self, result): - file_obj, filesize = result - file_obj.close() - assert filesize == self.TEST_FILESIZE - - d = self.listPath(self.service_name, os.path.dirname(self.filename.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.filename.replace('/', os.sep)) in filenames - - for entry in entries: - if os.path.basename(self.filename.replace('/', os.sep)) == entry.filename: - # The following asserts will fail if the remote machine's time is not in sync with the test machine's time - assert abs(entry.create_time - time.time()) < 3 - assert abs(entry.last_access_time - time.time()) < 3 - assert abs(entry.last_write_time - time.time()) < 3 - assert abs(entry.last_attr_change_time - time.time()) < 3 - break - - d = self.retrieveFile(self.service_name, self.filename, StringIO()) - d.addCallback(self.retrieveComplete) - d.addErrback(self.d.errback) - - def retrieveComplete(self, result): - file_obj, file_attributes, file_size = result - - md = MD5() - md.update(file_obj.getvalue()) - file_obj.close() - - assert file_size == self.TEST_FILESIZE - assert md.hexdigest() == self.TEST_DIGEST - - d = self.deleteFiles(self.service_name, self.filename) - d.addCallback(self.deleteComplete) - d.addErrback(self.d.errback) - - def deleteComplete(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.filename.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.filename.replace('/', os.sep)) not in filenames - self.d.callback(True) - self.instance.transport.loseConnection() - - def onAuthOK(self): - d = self.storeFile(self.service_name, self.filename, open(self.TEST_FILENAME, 'rb')) - d.addCallback(self.storeComplete) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=30.0) -def test_store_long_filename_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_long_filename_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_unicode_filename_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_unicode_filename_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/DirectSMBTwistedTests/util.py b/python3/tests/DirectSMBTwistedTests/util.py deleted file mode 100644 index 12f82af..0000000 --- a/python3/tests/DirectSMBTwistedTests/util.py +++ /dev/null @@ -1,19 +0,0 @@ - -import os -from ConfigParser import SafeConfigParser - -def getConnectionInfo(): - config_filename = os.path.join(os.path.dirname(__file__), os.path.pardir, 'connection.ini') - cp = SafeConfigParser() - cp.read(config_filename) - - info = { - 'server_name': cp.get('server', 'name'), - 'server_ip': cp.get('server', 'ip'), - 'server_port': cp.getint('server', 'direct_port'), - 'client_name': cp.get('client', 'name'), - 'user': cp.get('user', 'name'), - 'password': cp.get('user', 'password'), - 'is_direct_tcp': True, - } - return info diff --git a/python3/tests/NetBIOSTests/test_queryname.py b/python3/tests/NetBIOSTests/test_queryname.py index a727cba..4d13560 100644 --- a/python3/tests/NetBIOSTests/test_queryname.py +++ b/python3/tests/NetBIOSTests/test_queryname.py @@ -1,6 +1,6 @@ from nmb.NetBIOS import NetBIOS -from nose.tools import with_setup +from nose2.tools.decorators import with_teardown conn = None @@ -8,7 +8,7 @@ global conn conn.close() -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_broadcast(): global conn conn = NetBIOS() diff --git a/python3/tests/NetBIOSTwistedTests/__init__.py b/python3/tests/NetBIOSTwistedTests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python3/tests/NetBIOSTwistedTests/test_queryname.py b/python3/tests/NetBIOSTwistedTests/test_queryname.py deleted file mode 100644 index 43e4548..0000000 --- a/python3/tests/NetBIOSTwistedTests/test_queryname.py +++ /dev/null @@ -1,21 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from nmb.NetBIOSProtocol import NBNSProtocol -from nose.tools import with_setup - - -@deferred(timeout=15.0) -def test_broadcast(): - def cb(results): - assert results - - def cleanup(r): - p.transport.stopListening() - - p = NBNSProtocol() - d = p.queryName('MICHAEL-I5PC', timeout = 10) - d.addCallback(cb) - d.addBoth(cleanup) - - return d diff --git a/python3/tests/README.md b/python3/tests/README.md new file mode 100644 index 0000000..200a17e --- /dev/null +++ b/python3/tests/README.md @@ -0,0 +1,57 @@ + +Steps to Follow to Run the Unit Tests +===================================== + +## Step 1: Install system dependencies ## + +If you are using Ubuntu 20.04 LTS, you can install the system dependencies with the following command +``` +$> apt-get install python3-dev python3-venv gcc g++ make automake autoconf +``` +For other distributions, you can use their package managers and install the system dependencies (although the package names might differ slightly). + +## Step 2: Setup python virtualenv ## + +We will create a python3 virtualenv and install the python dependencies for testing in the "venv3" folder. + +``` +$> cd /python3 +$> virtualenv -p /usr/bin/python3 venv3 +$> source venv3/bin/activate +$venv3> pip install nose2 pyasn1 twisted +``` + +## Step 3: Setup shared folder on your remote SMB server ## + +Prepare a shared folder called "smbtest" on your remote SMB server (Windows or Samba). + +Then, download [smbtest.zip](https://miketeo.net/files/Projects/pysmb/smbtest.zip) and unzip the contents of this zip file in the shared folder. + +You should also configure a user on the SMB server with read-write access to the "smbtest" folder. + +## Step 4: Update connection details in connection.ini ## + +In the same folder where you are viewing this readme file, there should be an ini file called "connection.ini". Edit this file's connection details to match the shared folder's access information. + +## Step 5: Run the unit tests in the python3 folder ## + +Before running the tests, the venv3 virtualenv must be activated. +``` +$> cd /python3 +$> source venv3/bin/activate +``` + +To run all the tests: +``` + $venv3> nose2 -v tests +``` + +To selectively run some tests: +``` + $venv3> nose2 -v tests.SMBConnectionTests + $venv3> nose2 -v tests.SMBConnectionTests.test_rename + $venv3> nose2 -v tests.SMBConnectionTests.test_rename.test_rename_english_file_SMB1 +``` +For more information, please consult the [documentation for nose2](https://docs.nose2.io/). + + diff --git a/python3/tests/SMBConnectionTests/test_SMBHandler.py b/python3/tests/SMBConnectionTests/test_SMBHandler.py index 8fe5f02..9d2dcb3 100644 --- a/python3/tests/SMBConnectionTests/test_SMBHandler.py +++ b/python3/tests/SMBConnectionTests/test_SMBHandler.py @@ -2,7 +2,7 @@ import os, urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse, time, random from smb.SMBHandler import SMBHandler -from . import util +import util try: diff --git a/python3/tests/SMBConnectionTests/test_auth.py b/python3/tests/SMBConnectionTests/test_auth.py index 5e114b0..189d1b4 100644 --- a/python3/tests/SMBConnectionTests/test_auth.py +++ b/python3/tests/SMBConnectionTests/test_auth.py @@ -1,8 +1,8 @@ +from nose2.tools.decorators import with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -10,7 +10,7 @@ global conn conn.close() -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_NTLMv1_auth_SMB1(): global conn smb_structs.SUPPORT_SMB2 = False @@ -18,7 +18,7 @@ conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = False) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_NTLMv2_auth_SMB1(): global conn smb_structs.SUPPORT_SMB2 = False @@ -26,7 +26,7 @@ conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = True) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_NTLMv1_auth_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True @@ -34,7 +34,7 @@ conn = SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], domain = info['domain'], use_ntlm_v2 = False) assert conn.connect(info['server_ip'], info['server_port']) -@with_setup(teardown = teardown_func) +@with_teardown(teardown_func) def test_NTLMv2_auth_SMB2(): global conn smb_structs.SUPPORT_SMB2 = True diff --git a/python3/tests/SMBConnectionTests/test_createdeletedirectory.py b/python3/tests/SMBConnectionTests/test_createdeletedirectory.py index 814d89d..35e115f 100644 --- a/python3/tests/SMBConnectionTests/test_createdeletedirectory.py +++ b/python3/tests/SMBConnectionTests/test_createdeletedirectory.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import os, time, random +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -28,7 +28,8 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_english_directory_SMB1(): global conn @@ -45,7 +46,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_english_directory_SMB2(): global conn @@ -62,7 +64,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_unicode_directory_SMB1(): global conn @@ -79,7 +82,8 @@ names = [e.filename for e in entries] assert os.path.basename(path.replace('/', os.sep)) not in names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_unicode_directory_SMB2(): global conn diff --git a/python3/tests/SMBConnectionTests/test_deletepattern.py b/python3/tests/SMBConnectionTests/test_deletepattern.py index a7d183d..6fedf60 100644 --- a/python3/tests/SMBConnectionTests/test_deletepattern.py +++ b/python3/tests/SMBConnectionTests/test_deletepattern.py @@ -2,10 +2,10 @@ import os, time, random from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -29,18 +29,26 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) -def test_delete_SMB1(): - global conn - - path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) - conn.createDirectory('smbtest', path) - - for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: - conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) - - results = conn.listPath('smbtest', path) - filenames = list(map(lambda r: r.filename, results)) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_delete_without_subfolder_SMB1(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, BytesIO(b"0123456789")) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' in filenames assert 'aaBest.txt' in filenames assert 'aaTest.bin' in filenames @@ -51,6 +59,8 @@ results = conn.listPath('smbtest', path) filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' not in filenames assert 'aaBest.txt' not in filenames assert 'aaTest.bin' in filenames @@ -61,25 +71,82 @@ results = conn.listPath('smbtest', path) filenames = list(map(lambda r: r.filename, results)) - assert 'aaTest.bin' not in filenames - assert 'aaBest.bin' in filenames - assert 'random.txt' in filenames - - conn.deleteFiles('smbtest', path+'/*') - conn.deleteDirectory('smbtest', path) - -@with_setup(setup_func_SMB2, teardown_func) -def test_delete_SMB2(): - global conn - - path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) - conn.createDirectory('smbtest', path) - - for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: - conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) - - results = conn.listPath('smbtest', path) - filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_delete_with_subfolder_SMB1(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, BytesIO(b"0123456789")) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' in filenames + assert 'aaBest.txt' in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aa*.txt', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' not in filenames + assert 'aaBest.txt' not in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aaTest.*', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' not in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_delete_without_subfolder_SMB2(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, BytesIO(b"0123456789")) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' in filenames assert 'aaBest.txt' in filenames assert 'aaTest.bin' in filenames @@ -90,6 +157,8 @@ results = conn.listPath('smbtest', path) filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames assert 'aaTest.txt' not in filenames assert 'aaBest.txt' not in filenames assert 'aaTest.bin' in filenames @@ -100,6 +169,56 @@ results = conn.listPath('smbtest', path) filenames = list(map(lambda r: r.filename, results)) - assert 'aaTest.bin' not in filenames - assert 'aaBest.bin' in filenames - assert 'random.txt' in filenames + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_delete_with_subfolder_SMB2(): + global conn + + path = os.sep + u'testDelete %d-%d' % ( time.time(), random.randint(0, 1000) ) + conn.createDirectory('smbtest', path) + + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+filename, BytesIO(b"0123456789")) + + for p in [ 'aaTest.Folder', 'aaTest.Folder/xyz', 'bbTest.Folder' ]: + conn.createDirectory('smbtest', path+"/"+p) + for filename in [ 'aaTest.txt', 'aaBest.txt', 'aaTest.bin', 'aaBest.bin', 'random.txt' ]: + conn.storeFile('smbtest', path+"/"+p+"/"+filename, BytesIO(b"0123456789")) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' in filenames + assert 'aaBest.txt' in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aa*.txt', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.txt' not in filenames + assert 'aaBest.txt' not in filenames + assert 'aaTest.bin' in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames + + conn.deleteFiles('smbtest', path+'/aaTest.*', delete_matching_folders=True) + + results = conn.listPath('smbtest', path) + filenames = list(map(lambda r: r.filename, results)) + assert 'aaTest.Folder' not in filenames + assert 'bbTest.Folder' in filenames + assert 'aaTest.bin' not in filenames + assert 'aaBest.bin' in filenames + assert 'random.txt' in filenames diff --git a/python3/tests/SMBConnectionTests/test_echo.py b/python3/tests/SMBConnectionTests/test_echo.py index c81675d..f560a6f 100644 --- a/python3/tests/SMBConnectionTests/test_echo.py +++ b/python3/tests/SMBConnectionTests/test_echo.py @@ -1,8 +1,8 @@ import random +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection from .util import getConnectionInfo -from nose.tools import with_setup conn = None @@ -16,7 +16,8 @@ global conn conn.close() -@with_setup(setup_func, teardown_func) +@with_setup(setup_func) +@with_teardown(teardown_func) def test_echo(): global conn diff --git a/python3/tests/SMBConnectionTests/test_getattributes.py b/python3/tests/SMBConnectionTests/test_getattributes.py index c6e5d68..e2973ea 100644 --- a/python3/tests/SMBConnectionTests/test_getattributes.py +++ b/python3/tests/SMBConnectionTests/test_getattributes.py @@ -2,7 +2,7 @@ from smb.SMBConnection import SMBConnection from .util import getConnectionInfo -from nose.tools import with_setup +from nose2.tools.decorators import with_setup from smb import smb_structs conn = None diff --git a/python3/tests/SMBConnectionTests/test_listpath.py b/python3/tests/SMBConnectionTests/test_listpath.py index c3b08a7..46870bb 100644 --- a/python3/tests/SMBConnectionTests/test_listpath.py +++ b/python3/tests/SMBConnectionTests/test_listpath.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb.smb_constants import * +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -25,7 +26,8 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listPath_SMB1(): global conn results = conn.listPath('smbtest', '/') @@ -36,7 +38,8 @@ assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names assert ( 'rfc1001.txt', False ) in filenames # Test short English file names -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listSubPath_SMB1(): global conn results = conn.listPath('smbtest', '/Test Folder with Long Name/') @@ -45,7 +48,16 @@ assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', True ) in filenames -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathWithManyFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames))==999 + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listPath_SMB2(): global conn results = conn.listPath('smbtest', '/') @@ -56,7 +68,8 @@ assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names assert ( 'rfc1001.txt', False ) in filenames # Test short English file names -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listSubPath_SMB2(): global conn results = conn.listPath('smbtest', '/Test Folder with Long Name/') @@ -64,3 +77,109 @@ assert ( 'Test File.txt', False ) in filenames assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathWithManyFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive/') + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames))==999 + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterForDirectory_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterForDirectory_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_DIRECTORY) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert isDirectory + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterForFiles_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterForFiles_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL) + filenames = map(lambda r: ( r.filename, r.isDirectory ), results) + assert len(list(filenames)) > 0 + for f, isDirectory in filenames: + assert not isDirectory + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterPattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterPattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = 'Test*') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 2 + assert ( u'Test File.txt', False ) in filenames + assert ( u'Test Folder', True ) in filenames + assert ( u'子文件夹', True ) not in filenames + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterUnicodePattern_SMB1(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterUnicodePattern_SMB2(): + global conn + results = conn.listPath('smbtest', '/Test Folder with Long Name', pattern = u'*件夹') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + assert len(filenames) == 1 + assert ( u'Test File.txt', False ) not in filenames + assert ( u'Test Folder', True ) not in filenames + assert ( u'子文件夹', True ) in filenames + +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) +def test_listPathFilterEmptyList_SMB1(): + global conn + results = conn.listPath('smbtest', '/RFC Archive', pattern = '*.abc') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) + +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) +def test_listPathFilterEmptyList_SMB2(): + global conn + results = conn.listPath('smbtest', '/RFC Archive', pattern = '*.abc') + filenames = list(map(lambda r: ( r.filename, r.isDirectory ), results)) diff --git a/python3/tests/SMBConnectionTests/test_listshares.py b/python3/tests/SMBConnectionTests/test_listshares.py index 23328ec..431b043 100644 --- a/python3/tests/SMBConnectionTests/test_listshares.py +++ b/python3/tests/SMBConnectionTests/test_listshares.py @@ -1,8 +1,8 @@ +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -24,13 +24,15 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listshares_SMB1(): global conn results = conn.listShares() assert 'smbtest' in [r.name.lower() for r in results] -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listshares_SMB2(): global conn results = conn.listShares() diff --git a/python3/tests/SMBConnectionTests/test_listsnapshots.py b/python3/tests/SMBConnectionTests/test_listsnapshots.py index 551581f..1ad2830 100644 --- a/python3/tests/SMBConnectionTests/test_listsnapshots.py +++ b/python3/tests/SMBConnectionTests/test_listsnapshots.py @@ -1,8 +1,8 @@ +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -24,13 +24,15 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_listsnapshots_SMB1(): global conn results = conn.listSnapshots('smbtest', '/rfc1001.txt') assert len(results) > 0 -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_listsnapshots_SMB2(): global conn results = conn.listSnapshots('smbtest', '/rfc1001.txt') diff --git a/python3/tests/SMBConnectionTests/test_rename.py b/python3/tests/SMBConnectionTests/test_rename.py index cb6d880..b9fec70 100644 --- a/python3/tests/SMBConnectionTests/test_rename.py +++ b/python3/tests/SMBConnectionTests/test_rename.py @@ -2,10 +2,10 @@ import os, time, random from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs conn = None @@ -27,7 +27,8 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_english_file_SMB1(): global conn @@ -50,7 +51,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_english_file_SMB2(): global conn @@ -73,7 +75,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_unicode_file_SMB1(): global conn @@ -96,7 +99,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_unicode_file_SMB2(): global conn @@ -119,7 +123,8 @@ conn.deleteFiles('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_english_directory_SMB1(): global conn @@ -142,7 +147,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_english_directory_SMB2(): global conn @@ -165,7 +171,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_rename_unicode_directory_SMB1(): global conn @@ -188,7 +195,8 @@ conn.deleteDirectory('smbtest', new_path) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_rename_unicode_directory_SMB2(): global conn diff --git a/python3/tests/SMBConnectionTests/test_retrievefile.py b/python3/tests/SMBConnectionTests/test_retrievefile.py index e49ab8f..fc1bb11 100644 --- a/python3/tests/SMBConnectionTests/test_retrievefile.py +++ b/python3/tests/SMBConnectionTests/test_retrievefile.py @@ -2,10 +2,10 @@ import os, tempfile from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs try: import hashlib @@ -34,7 +34,8 @@ global conn conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_multiplereads_SMB1(): # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) global conn @@ -48,7 +49,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_multiplereads_SMB2(): # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) global conn @@ -62,7 +64,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_longfilename_SMB1(): # Test file retrieval that has a long English filename global conn @@ -76,7 +79,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_longfilename_SMB2(): # Test file retrieval that has a long English filename global conn @@ -90,7 +94,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_unicodefilename_SMB1(): # Test file retrieval that has a long non-English filename inside a folder with a non-English name global conn @@ -104,7 +109,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_unicodefilename_SMB2(): # Test file retrieval that has a long non-English filename inside a folder with a non-English name global conn @@ -118,7 +124,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_SMB1(): # Test file retrieval from offset to EOF global conn @@ -132,7 +139,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_SMB2(): # Test file retrieval from offset to EOF global conn @@ -146,7 +154,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_biglimit_SMB1(): # Test file retrieval from offset with a big max_length global conn @@ -160,7 +169,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_biglimit_SMB2(): # Test file retrieval from offset with a big max_length global conn @@ -174,7 +184,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_smalllimit_SMB1(): # Test file retrieval from offset with a small max_length global conn @@ -188,7 +199,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_smalllimit_SMB2(): # Test file retrieval from offset with a small max_length global conn @@ -202,7 +214,8 @@ temp_fh.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_retr_offset_and_zerolimit_SMB1(): # Test file retrieval from offset to EOF with max_length=0 global conn @@ -216,7 +229,8 @@ temp_fh.close() -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_retr_offset_and_zerolimit_SMB2(): # Test file retrieval from offset to EOF with max_length=0 global conn diff --git a/python3/tests/SMBConnectionTests/test_storefile.py b/python3/tests/SMBConnectionTests/test_storefile.py index 2e8e0b8..22c7dd7 100644 --- a/python3/tests/SMBConnectionTests/test_storefile.py +++ b/python3/tests/SMBConnectionTests/test_storefile.py @@ -2,10 +2,10 @@ import os, tempfile, random, time from io import BytesIO +from nose2.tools.decorators import with_setup, with_teardown from smb.SMBConnection import SMBConnection +from smb import smb_structs from .util import getConnectionInfo -from nose.tools import with_setup -from smb import smb_structs try: import hashlib @@ -41,7 +41,8 @@ conn.close() -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_store_long_filename_SMB1(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -64,7 +65,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_store_long_filename_SMB2(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -87,7 +89,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_store_unicode_filename_SMB1(): filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -110,7 +113,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB1, teardown_func) +@with_setup(setup_func_SMB1) +@with_teardown(teardown_func) def test_store_from_offset_SMB1(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -130,7 +134,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_store_unicode_filename_SMB2(): filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) @@ -152,7 +157,8 @@ conn.deleteFiles('smbtest', filename) -@with_setup(setup_func_SMB2, teardown_func) +@with_setup(setup_func_SMB2) +@with_teardown(teardown_func) def test_store_from_offset_SMB2(): filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) diff --git a/python3/tests/SMBConnectionTests/test_with_context.py b/python3/tests/SMBConnectionTests/test_with_context.py new file mode 100644 index 0000000..58d9567 --- /dev/null +++ b/python3/tests/SMBConnectionTests/test_with_context.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from smb.SMBConnection import SMBConnection +from .util import getConnectionInfo + +def test_context(): + info = getConnectionInfo() + with SMBConnection(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) as conn: + assert conn.connect(info['server_ip'], info['server_port']) + + assert conn.sock is None diff --git a/python3/tests/SMBTwistedTests/__init__.py b/python3/tests/SMBTwistedTests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python3/tests/SMBTwistedTests/test_auth.py b/python3/tests/SMBTwistedTests/test_auth.py deleted file mode 100644 index 967a117..0000000 --- a/python3/tests/SMBTwistedTests/test_auth.py +++ /dev/null @@ -1,77 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class AuthFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - self.d.callback(True) - - def onAuthFailed(self): - self.d.callback(False) - - -@deferred(timeout=5.0) -def test_NTLMv1_auth_SMB1(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = False - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv2_auth_SMB1(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = False - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv1_auth_SMB2(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = True - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = False) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - - -@deferred(timeout=5.0) -def test_NTLMv2_auth_SMB2(): - def result(auth_passed): - assert auth_passed - - smb_structs.SUPPORT_SMB2 = True - info = getConnectionInfo() - factory = AuthFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.d.addCallback(result) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_createdeletedirectory.py b/python3/tests/SMBTwistedTests/test_createdeletedirectory.py deleted file mode 100644 index 8874da8..0000000 --- a/python3/tests/SMBTwistedTests/test_createdeletedirectory.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, random, time -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class DirectoryFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = '' - self.path = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def createDone(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.path.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - names = [e.filename for e in entries] - assert os.path.basename(self.path.replace('/', os.sep)) in names - - d = self.deleteDirectory(self.service_name, self.path) - d.addCallback(self.deleteDone) - d.addErrback(self.d.errback) - - def deleteDone(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.path.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - names = [e.filename for e in entries] - assert os.path.basename(self.path.replace('/', os.sep)) not in names - self.d.callback(True) - - def onAuthOK(self): - d = self.createDirectory(self.service_name, self.path) - d.addCallback(self.createDone) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_english_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + 'TestDir %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_english_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + 'TestDir %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_unicode_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + '文件夹创建 %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_unicode_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = DirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = os.sep + '文件夹创建 %d-%d' % ( time.time(), random.randint(0, 1000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_echo.py b/python3/tests/SMBTwistedTests/test_echo.py deleted file mode 100644 index b781eee..0000000 --- a/python3/tests/SMBTwistedTests/test_echo.py +++ /dev/null @@ -1,39 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from .util import getConnectionInfo - - -class EchoFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.echo_data = 'This is an echo test' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(data): - assert data == self.echo_data - self.d.callback(True) - - d = self.echo(self.echo_data) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_echo(): - info = getConnectionInfo() - factory = EchoFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_getattributes.py b/python3/tests/SMBTwistedTests/test_getattributes.py deleted file mode 100644 index f84450b..0000000 --- a/python3/tests/SMBTwistedTests/test_getattributes.py +++ /dev/null @@ -1,100 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from util import getConnectionInfo - - -class GetAttributesFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.path = '' - self.is_directory = False - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(info): - assert info.isDirectory == self.is_directory - self.d.callback(True) - - d = self.getAttributes('smbtest', self.path, timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_getAttributes_SMB1_test1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = '/Test Folder with Long Name/' - factory.is_directory = True - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_getAttributes_SMB1_test2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = '/rfc1001.txt' - factory.is_directory = False - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_getAttributes_SMB1_test3(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = u'/\u6d4b\u8bd5\u6587\u4ef6\u5939' - factory.is_directory = True - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_getAttributes_SMB2_test1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = '/Test Folder with Long Name/' - factory.is_directory = True - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_getAttributes_SMB2_test2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = '/rfc1001.txt' - factory.is_directory = False - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_getAttributes_SMB2_test3(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = GetAttributesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.path = u'/\u6d4b\u8bd5\u6587\u4ef6\u5939' - factory.is_directory = True - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_listpath.py b/python3/tests/SMBTwistedTests/test_listpath.py deleted file mode 100644 index a827955..0000000 --- a/python3/tests/SMBTwistedTests/test_listpath.py +++ /dev/null @@ -1,56 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListPathFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - filenames = [( r.filename, r.isDirectory ) for r in results] - assert ( '\u6d4b\u8bd5\u6587\u4ef6\u5939', True ) in filenames # Test non-English folder names - assert ( 'Test Folder with Long Name', True ) in filenames # Test long English folder names - assert ( 'TestDir1', True ) in filenames # Test short English folder names - assert ( 'Implementing CIFS - SMB.html', False ) in filenames # Test long English file names - assert ( 'rfc1001.txt', False ) in filenames # Test short English file names - - self.d.callback(True) - - d = self.listPath('smbtest', '/', timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listPath_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListPathFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listPath_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListPathFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_listshares.py b/python3/tests/SMBTwistedTests/test_listshares.py deleted file mode 100644 index 9c35919..0000000 --- a/python3/tests/SMBTwistedTests/test_listshares.py +++ /dev/null @@ -1,51 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListSharesFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - assert 'smbtest' in [r.name.lower() for r in results] - self.d.callback(True) - self.instance.transport.loseConnection() - - d = self.listShares(timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listshares_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListSharesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listshares_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListSharesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_listsnapshots.py b/python3/tests/SMBTwistedTests/test_listsnapshots.py deleted file mode 100644 index 7c3a5b6..0000000 --- a/python3/tests/SMBTwistedTests/test_listsnapshots.py +++ /dev/null @@ -1,57 +0,0 @@ - -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class ListSnapshotsFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = None - self.path = None - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def onAuthOK(self): - def cb(results): - assert len(results) > 0 - self.d.callback(True) - self.instance.transport.loseConnection() - - d = self.listSnapshots(self.service_name, self.path, timeout = 15) - d.addCallback(cb) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=15.0) -def test_listshares_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = ListSnapshotsFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = '/rfc1001.txt' - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=15.0) -def test_listshares_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = ListSnapshotsFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.path = '/rfc1001.txt' - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_rename.py b/python3/tests/SMBTwistedTests/test_rename.py deleted file mode 100644 index 2658d79..0000000 --- a/python3/tests/SMBTwistedTests/test_rename.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, random, time -from io import StringIO -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - - -class RenameFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service = '' - self.new_path = '' - self.old_path = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def pathCreated(self, result): - d = self.listPath(self.service, os.path.dirname(self.old_path.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.old_path.replace('/', os.sep)) in filenames - assert os.path.basename(self.new_path.replace('/', os.sep)) not in filenames - - d = self.rename(self.service, self.old_path, self.new_path) - d.addCallback(self.renameComplete) - d.addErrback(self.d.errback) - - def renameComplete(self, result): - d = self.listPath(self.service, os.path.dirname(self.new_path.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.new_path.replace('/', os.sep)) in filenames - assert os.path.basename(self.old_path.replace('/', os.sep)) not in filenames - self.cleanup() - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -class RenameFileFactory(RenameFactory): - - def onAuthOK(self): - d = self.storeFile(self.service, self.old_path, StringIO('Rename file test')) - d.addCallback(self.pathCreated) - d.addErrback(self.d.errback) - - def cleanup(self): - d = self.deleteFiles(self.service, self.new_path) - d.chainDeferred(self.d) - - -class RenameDirectoryFactory(RenameFactory): - - def onAuthOK(self): - d = self.createDirectory(self.service, self.old_path) - d.addCallback(self.pathCreated) - d.addErrback(self.d.errback) - - def cleanup(self): - d = self.deleteDirectory(self.service, self.new_path) - d.chainDeferred(self.d) - - -@deferred(timeout=30.0) -def test_rename_english_file_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_file_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_file_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_file_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d.txt' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_english_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/RenameTest %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_directory_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_rename_unicode_directory_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RenameDirectoryFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.old_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - factory.new_path = '/改名测试 %d-%d' % ( time.time(), random.randint(1000, 9999) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_retrievefile.py b/python3/tests/SMBTwistedTests/test_retrievefile.py deleted file mode 100644 index bbfc7d0..0000000 --- a/python3/tests/SMBTwistedTests/test_retrievefile.py +++ /dev/null @@ -1,278 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, tempfile -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - -try: - import hashlib - def MD5(): return hashlib.md5() -except ImportError: - import md5 - def MD5(): return md5.new() - - -class RetrieveFileFactory(SMBProtocolFactory): - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.temp_fh = tempfile.NamedTemporaryFile(prefix = 'pysmbtest-') - self.service = '' - self.path = '' - self.digest = '' - self.offset = 0 - self.max_length = -1 - self.filesize = 0 - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def fileRetrieved(self, write_result): - file_obj, file_attributes, file_size = write_result - assert file_obj == self.temp_fh - - md = MD5() - filesize = 0 - self.temp_fh.seek(0) - while True: - s = self.temp_fh.read(8192) - if not s: - break - md.update(s) - filesize += len(s) - - assert self.filesize == filesize - assert md.hexdigest() == self.digest - - self.temp_fh.close() - self.d.callback(True) - self.instance.transport.loseConnection() - - def onAuthOK(self): - assert self.service - assert self.path - - d = self.retrieveFileFromOffset(self.service, self.path, self.temp_fh, self.offset, self.max_length, timeout = 15) - d.addCallback(self.fileRetrieved) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=30.0) -def test_retr_multiplereads_SMB1(): - # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/rfc1001.txt' - factory.digest = '5367c2bbf97f521059c78eab65309ad3' - factory.filesize = 158437 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_multiplereads_SMB2(): - # Test file retrieval using multiple ReadAndx calls (assuming each call will not reach more than 65534 bytes) - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/rfc1001.txt' - factory.digest = '5367c2bbf97f521059c78eab65309ad3' - factory.filesize = 158437 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_longfilename_SMB1(): - # Test file retrieval that has a long English filename - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/Implementing CIFS - SMB.html' - factory.digest = '671c5700d279fcbbf958c1bba3c2639e' - factory.filesize = 421269 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_longfilename_SMB2(): - # Test file retrieval that has a long English filename - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/Implementing CIFS - SMB.html' - factory.digest = '671c5700d279fcbbf958c1bba3c2639e' - factory.filesize = 421269 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_unicodefilename_SMB1(): - # Test file retrieval that has a long non-English filename inside a folder with a non-English name - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '8a44c1e80d55e91c92350955cdf83442' - factory.filesize = 256000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_unicodefilename_SMB2(): - # Test file retrieval that has a long non-English filename inside a folder with a non-English name - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '8a44c1e80d55e91c92350955cdf83442' - factory.filesize = 256000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_SMB1(): - # Test file retrieval from offset to EOF - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'a141bd8024571ce7cb5c67f2b0d8ea0b' - factory.filesize = 156000 - factory.offset = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_SMB2(): - # Test file retrieval from offset to EOF - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'a141bd8024571ce7cb5c67f2b0d8ea0b' - factory.filesize = 156000 - factory.offset = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_biglimit_SMB1(): - # Test file retrieval from offset with a big max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '83b7afd7c92cdece3975338b5ca0b1c5' - factory.filesize = 100000 - factory.offset = 100000 - factory.max_length = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_biglimit_SMB2(): - # Test file retrieval from offset with a big max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '83b7afd7c92cdece3975338b5ca0b1c5' - factory.filesize = 100000 - factory.offset = 100000 - factory.max_length = 100000 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_smalllimit_SMB1(): - # Test file retrieval from offset with a small max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '746f60a96b39b712a7b6e17ddde19986' - factory.filesize = 10 - factory.offset = 100000 - factory.max_length = 10 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_smalllimit_SMB2(): - # Test file retrieval from offset with a small max_length - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = '746f60a96b39b712a7b6e17ddde19986' - factory.filesize = 10 - factory.offset = 100000 - factory.max_length = 10 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_zerolimit_SMB1(): - # Test file retrieval from offset to EOF with max_length=0 - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'd41d8cd98f00b204e9800998ecf8427e' - factory.filesize = 0 - factory.offset = 100000 - factory.max_length = 0 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_retr_offset_and_zerolimit_SMB2(): - # Test file retrieval from offset to EOF with max_length=0 - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = RetrieveFileFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service = 'smbtest' - factory.path = '/测试文件夹/垃圾文件.dat' - factory.digest = 'd41d8cd98f00b204e9800998ecf8427e' - factory.filesize = 0 - factory.offset = 100000 - factory.max_length = 0 - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/test_storefile.py b/python3/tests/SMBTwistedTests/test_storefile.py deleted file mode 100644 index 0d5e6d0..0000000 --- a/python3/tests/SMBTwistedTests/test_storefile.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- - -import os, time, random -from io import StringIO -from nose.twistedtools import reactor, deferred -from twisted.internet import defer -from smb.SMBProtocol import SMBProtocolFactory -from smb import smb_structs -from .util import getConnectionInfo - -try: - import hashlib - def MD5(): return hashlib.md5() -except ImportError: - import md5 - def MD5(): return md5.new() - -class StoreFilesFactory(SMBProtocolFactory): - """ - A super test factory that tests store file, list files, retrieve file and delete file functionlities in sequence. - """ - - TEST_FILENAME = os.path.join(os.path.dirname(__file__), os.pardir, 'SupportFiles', 'binary.dat') - TEST_FILESIZE = 256000 - TEST_DIGEST = 'bb6303f76e29f354b6fdf6ef58587e48' - - def __init__(self, *args, **kwargs): - SMBProtocolFactory.__init__(self, *args, **kwargs) - self.d = defer.Deferred() - self.d.addBoth(self.testDone) - self.service_name = '' - self.filename = '' - - def testDone(self, r): - if self.instance: - self.instance.transport.loseConnection() - return r - - def storeComplete(self, result): - file_obj, filesize = result - file_obj.close() - assert filesize == self.TEST_FILESIZE - - d = self.listPath(self.service_name, os.path.dirname(self.filename.replace('/', os.sep))) - d.addCallback(self.listComplete) - d.addErrback(self.d.errback) - - def listComplete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.filename.replace('/', os.sep)) in filenames - - for entry in entries: - if os.path.basename(self.filename.replace('/', os.sep)) == entry.filename: - # The following asserts will fail if the remote machine's time is not in sync with the test machine's time - assert abs(entry.create_time - time.time()) < 3 - assert abs(entry.last_access_time - time.time()) < 3 - assert abs(entry.last_write_time - time.time()) < 3 - assert abs(entry.last_attr_change_time - time.time()) < 3 - break - - d = self.retrieveFile(self.service_name, self.filename, StringIO()) - d.addCallback(self.retrieveComplete) - d.addErrback(self.d.errback) - - def retrieveComplete(self, result): - file_obj, file_attributes, file_size = result - - md = MD5() - md.update(file_obj.getvalue()) - file_obj.close() - - assert file_size == self.TEST_FILESIZE - assert md.hexdigest() == self.TEST_DIGEST - - d = self.deleteFiles(self.service_name, self.filename) - d.addCallback(self.deleteComplete) - d.addErrback(self.d.errback) - - def deleteComplete(self, result): - d = self.listPath(self.service_name, os.path.dirname(self.filename.replace('/', os.sep))) - d.addCallback(self.list2Complete) - d.addErrback(self.d.errback) - - def list2Complete(self, entries): - filenames = [e.filename for e in entries] - assert os.path.basename(self.filename.replace('/', os.sep)) not in filenames - self.d.callback(True) - self.instance.transport.loseConnection() - - def onAuthOK(self): - d = self.storeFile(self.service_name, self.filename, open(self.TEST_FILENAME, 'rb')) - d.addCallback(self.storeComplete) - d.addErrback(self.d.errback) - - def onAuthFailed(self): - self.d.errback('Auth failed') - - -@deferred(timeout=30.0) -def test_store_long_filename_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_long_filename_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + 'StoreTest %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_unicode_filename_SMB1(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = False - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d - -@deferred(timeout=30.0) -def test_store_unicode_filename_SMB2(): - info = getConnectionInfo() - smb_structs.SUPPORT_SMB2 = True - - factory = StoreFilesFactory(info['user'], info['password'], info['client_name'], info['server_name'], use_ntlm_v2 = True) - factory.service_name = 'smbtest' - factory.filename = os.sep + '上载测试 %d-%d.dat' % ( time.time(), random.randint(0, 10000) ) - reactor.connectTCP(info['server_ip'], info['server_port'], factory) - return factory.d diff --git a/python3/tests/SMBTwistedTests/util.py b/python3/tests/SMBTwistedTests/util.py deleted file mode 100644 index 37ff2f2..0000000 --- a/python3/tests/SMBTwistedTests/util.py +++ /dev/null @@ -1,19 +0,0 @@ - -import os -from configparser import SafeConfigParser - -def getConnectionInfo(): - config_filename = os.path.join(os.path.dirname(__file__), os.path.pardir, 'connection.ini') - cp = SafeConfigParser() - cp.read(config_filename) - - info = { - 'server_name': cp.get('server', 'name'), - 'server_ip': cp.get('server', 'ip'), - 'server_port': cp.getint('server', 'port'), - 'client_name': cp.get('client', 'name'), - 'user': cp.get('user', 'name'), - 'password': cp.get('user', 'password'), - } - return info - diff --git a/python3/tests/connection.ini b/python3/tests/connection.ini index 96d2652..d8d249d 100644 --- a/python3/tests/connection.ini +++ b/python3/tests/connection.ini @@ -3,6 +3,7 @@ name = SERVER ip = 192.168.1.1 port = 139 +direct_port = 445 [client] name = TESTCLIENT diff --git a/python3/tests/smbtest.7z b/python3/tests/smbtest.7z deleted file mode 100644 index c5c9f42..0000000 Binary files a/python3/tests/smbtest.7z and /dev/null differ diff --git a/python3/tests/test_security_descriptors.py b/python3/tests/test_security_descriptors.py new file mode 100644 index 0000000..7a87a84 --- /dev/null +++ b/python3/tests/test_security_descriptors.py @@ -0,0 +1,139 @@ +import binascii + +from smb import security_descriptors as sd +from smb import smb_constants as sc + + +def test_sid_string_representation(): + sid = sd.SID(1, 5, [2, 3, 4]) + assert str(sid) == "S-1-5-2-3-4" + sid = sd.SID(1, 2**32 + 3, []) + assert str(sid) == "S-1-0x100000003" + sid = sd.SID(1, 2**32, [3, 2, 1]) + assert str(sid) == "S-1-0x100000000-3-2-1" + + +def test_sid_binary_parsing(): + raw_sid = binascii.unhexlify(b""" + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, b' \n')) + assert str(sd.SID.from_bytes(raw_sid)) == "S-1-5-21-717312990-3403304746-849770945-1201" + raw_sid += b"garbage" + assert str(sd.SID.from_bytes(raw_sid)) == "S-1-5-21-717312990-3403304746-849770945-1201" + sid, tail = sd.SID.from_bytes(raw_sid, return_tail=True) + assert str(sid) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert tail == b"garbage" + + +def test_ace_binary_parsing(): + raw_ace = binascii.unhexlify(b""" + 00 10 24 00 ff 01 1f 00 01 05 00 00 00 00 00 05 + 15 00 00 00 de 53 c1 2a 2a 4f da ca c1 79 a6 32 + 6e 04 00 00 + """.translate(None, b' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1134" + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.WRITE_OWNER | sc.WRITE_DAC + | sc.READ_CONTROL | sc.DELETE | sc.FILE_READ_DATA + | sc.FILE_WRITE_DATA | sc.FILE_APPEND_DATA + | sc.FILE_READ_EA | sc.FILE_WRITE_EA | sc.FILE_EXECUTE + | sc.FILE_DELETE_CHILD | sc.FILE_READ_ATTRIBUTES + | sc.FILE_WRITE_ATTRIBUTES) + assert not ace.additional_data + + raw_ace = binascii.unhexlify(b""" + 00 13 18 00 a9 00 12 00 01 02 00 00 00 00 00 05 + 20 00 00 00 21 02 00 00 + """.translate(None, b' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-32-545" + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert ace.flags == (sd.ACE_FLAG_INHERITED | sd.ACE_FLAG_CONTAINER_INHERIT + | sd.ACE_FLAG_OBJECT_INHERIT) + assert ace.mask == (sc.SYNCHRONIZE | sc.READ_CONTROL | sc.FILE_READ_DATA + | sc.FILE_READ_EA | sc.FILE_EXECUTE + | sc.FILE_READ_ATTRIBUTES) + assert not ace.additional_data + + raw_ace = binascii.unhexlify(b""" + 01 03 24 00 a9 00 02 00 01 05 00 00 00 00 00 05 + 15 00 00 00 de 53 c1 2a 2a 4f da ca c1 79 a6 32 + 6c 04 00 00 + """.translate(None, b' \n')) + ace = sd.ACE.from_bytes(raw_ace) + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1132" + assert ace.type == sd.ACE_TYPE_ACCESS_DENIED + assert ace.flags == (sd.ACE_FLAG_CONTAINER_INHERIT + | sd.ACE_FLAG_OBJECT_INHERIT) + assert ace.mask == (sc.READ_CONTROL | sc.FILE_READ_DATA | sc.FILE_READ_EA + | sc.FILE_EXECUTE | sc.FILE_READ_ATTRIBUTES) + assert not ace.additional_data + + +def test_acl_binary_parsing(): + raw_acl = binascii.unhexlify(b""" + 02 00 70 00 04 00 00 00 00 10 18 00 89 00 10 00 + 01 02 00 00 00 00 00 05 20 00 00 00 21 02 00 00 + 00 10 14 00 ff 01 1f 00 01 01 00 00 00 00 00 05 + 12 00 00 00 00 10 18 00 ff 01 1f 00 01 02 00 00 + 00 00 00 05 20 00 00 00 20 02 00 00 00 10 24 00 + ff 01 1f 00 01 05 00 00 00 00 00 05 15 00 00 00 + de 53 c1 2a 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, b' \n')) + acl = sd.ACL.from_bytes(raw_acl) + assert acl.revision == 2 + assert len(acl.aces) == 4 + + ace = acl.aces[0] + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert str(ace.sid) == "S-1-5-32-545" + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.FILE_READ_DATA | sc.FILE_READ_EA + | sc.FILE_READ_ATTRIBUTES) + + ace = acl.aces[3] + assert ace.type == sd.ACE_TYPE_ACCESS_ALLOWED + assert str(ace.sid) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert ace.flags == sd.ACE_FLAG_INHERITED + assert ace.mask == (sc.SYNCHRONIZE | sc.WRITE_OWNER | sc.WRITE_DAC + | sc.READ_CONTROL | sc.DELETE | sc.FILE_READ_DATA + | sc.FILE_WRITE_DATA | sc.FILE_APPEND_DATA + | sc.FILE_READ_EA | sc.FILE_WRITE_EA | sc.FILE_EXECUTE + | sc.FILE_DELETE_CHILD | sc.FILE_READ_ATTRIBUTES + | sc.FILE_WRITE_ATTRIBUTES) + + +def test_descriptor_binary_parsing(): + raw_descriptor = binascii.unhexlify(b""" + 01 00 04 84 14 00 00 00 30 00 00 00 00 00 00 00 + 4c 00 00 00 01 05 00 00 00 00 00 05 15 00 00 00 + de 53 c1 2a 2a 4f da ca c1 79 a6 32 b1 04 00 00 + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 01 02 00 00 02 00 70 00 + 04 00 00 00 00 10 18 00 89 00 10 00 01 02 00 00 + 00 00 00 05 20 00 00 00 21 02 00 00 00 10 14 00 + ff 01 1f 00 01 01 00 00 00 00 00 05 12 00 00 00 + 00 10 18 00 ff 01 1f 00 01 02 00 00 00 00 00 05 + 20 00 00 00 20 02 00 00 00 10 24 00 ff 01 1f 00 + 01 05 00 00 00 00 00 05 15 00 00 00 de 53 c1 2a + 2a 4f da ca c1 79 a6 32 b1 04 00 00 + """.translate(None, b' \n')) + descriptor = sd.SecurityDescriptor.from_bytes(raw_descriptor) + assert descriptor.flags == (sd.SECURITY_DESCRIPTOR_SELF_RELATIVE + | sd.SECURITY_DESCRIPTOR_DACL_PRESENT + | sd.SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED) + assert descriptor.dacl is not None + assert descriptor.sacl is None + assert str(descriptor.owner) == "S-1-5-21-717312990-3403304746-849770945-1201" + assert str(descriptor.group) == "S-1-5-21-717312990-3403304746-849770945-513" + + acl = descriptor.dacl + assert acl.revision == 2 + assert len(acl.aces) == 4 + assert str(acl.aces[0].sid) == sd.SID_BUILTIN_USERS + assert str(acl.aces[1].sid) == sd.SID_LOCAL_SYSTEM + assert str(acl.aces[2].sid) == sd.SID_BUILTIN_ADMINISTRATORS + assert str(acl.aces[3].sid) == "S-1-5-21-717312990-3403304746-849770945-1201" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8bfd5a1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff --git a/setup.py b/setup.py index e9093d7..234bc2f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name = "pysmb", - version = "1.1.19", + version = "1.2.6", author = "Michael Teo", author_email = "miketeo@miketeo.net", license = "zlib/libpng", diff --git a/sphinx/requirements.txt b/sphinx/requirements.txt new file mode 100644 index 0000000..c14b1c8 --- /dev/null +++ b/sphinx/requirements.txt @@ -0,0 +1,2 @@ +twisted>=15.0.0 +pyasn1>=0.3.0 diff --git a/sphinx/source/api/smb_SMBHandler.rst b/sphinx/source/api/smb_SMBHandler.rst index da106fe..9300de1 100644 --- a/sphinx/source/api/smb_SMBHandler.rst +++ b/sphinx/source/api/smb_SMBHandler.rst @@ -6,8 +6,12 @@ Notes ----- -* Note that you need to pass in a valid hostname or IP address for the host component of the URL. - Do not use the Windows/NetBIOS machine name for the host component. +* The host component of the URL must be one of the following: + + * A fully-qualified hostname that can be resolved by your local DNS service. Example: myserver.test.com + * An IP address. Example: 192.168.1.1 + * A comma-separated string "," where ** is the Windows/NetBIOS machine name for remote SMB service, and ** is the service's IP address. Example: MYSERVER,192.168.1.1 + * The first component of the path in the URL points to the name of the shared folder. Subsequent path components will point to the directory/folder of the file. * You can retrieve and upload files, but you cannot delete files/folders or create folders. @@ -16,7 +20,7 @@ Example ------- -The following code snippet illustrates file retrieval.:: +The following code snippet illustrates file retrieval with Python 2.:: # -*- coding: utf-8 -*- import urllib2 @@ -34,7 +38,7 @@ # Process fh2 like a file-like object and then close it. fh2.close() -The following code snippet illustrates file upload. You need to provide a file-like object for the *data* parameter in the *open()* method:: +The following code snippet illustrates file upload with Python 2. You need to provide a file-like object for the *data* parameter in the *open()* method:: import urllib2 from smb.SMBHandler import SMBHandler @@ -46,3 +50,34 @@ # Reading from fh will only return an empty string fh.close() + + +The following code snippet illustrates file retrieval with Python 3.:: + + import urllib + from smb.SMBHandler import SMBHandler + + director = urllib.request.build_opener(SMBHandler) + fh = director.open('smb://myuserID:mypassword@192.168.1.1/sharedfolder/rfc1001.txt') + + # Process fh like a file-like object and then close it. + fh.close() + + # For paths/files with unicode characters, simply pass in the URL as an unicode string + fh2 = director.open(u'smb://myuserID:mypassword@192.168.1.1/sharedfolder/测试文件夹/垃圾文件.dat') + + # Process fh2 like a file-like object and then close it. + fh2.close() + +The following code snippet illustrates file upload with Python 3. You need to provide a file-like object for the *data* parameter in the *open()* method:: + + import urllib + from smb.SMBHandler import SMBHandler + + file_fh = open('local_file.dat', 'rb') + + director = urllib.request.build_opener(SMBHandler) + fh = director.open('smb://myuserID:mypassword@192.168.1.1/sharedfolder/upload_file.dat', data = file_fh) + + # Reading from fh will only return an empty string + fh.close() diff --git a/sphinx/source/api/smb_security_descriptors.rst b/sphinx/source/api/smb_security_descriptors.rst new file mode 100644 index 0000000..0f048fe --- /dev/null +++ b/sphinx/source/api/smb_security_descriptors.rst @@ -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/sphinx/source/conf.py b/sphinx/source/conf.py index 12ec1b9..4e4cb50 100644 --- a/sphinx/source/conf.py +++ b/sphinx/source/conf.py @@ -1,12 +1,4 @@ # -*- coding: utf-8 -*- -# -# pysmb documentation build configuration file, created by -# sphinx-quickstart on Sun Dec 18 15:54:40 2011. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. @@ -17,7 +9,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'python2')) +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'python3')) # -- General configuration ----------------------------------------------------- @@ -42,16 +34,16 @@ # General information about the project. project = u'pysmb' -copyright = u'2001-2015, Michael Teo http://miketeo.net/' +copyright = u'2001-2021, Michael Teo https://miketeo.net/' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '1.1.18' +version = '1.2.6' # The full version, including alpha/beta/rc tags. -release = '1.1.18' +release = '1.2.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/sphinx/source/extending.rst b/sphinx/source/extending.rst index 9945212..c39572c 100644 --- a/sphinx/source/extending.rst +++ b/sphinx/source/extending.rst @@ -14,6 +14,7 @@ 3. Write your own loop handling method to read data from the socket. Once data have been read, call *feedData* method with the parameter. The *feedData* method has its own internal buffer, so it can accept incomplete NetBIOS session packet data. 4. Override + * *onAuthOK* method to include your own operations to perform when authentication is successful. You can initiate file operations in this method. * *onAuthFailed* method to include your own processing on what to do when authentication fails. You can report this as an error, or to try a different NTLM authentication algorithm (*use_ntlm_v2* parameter in the constructor). * *onNMBSessionFailed* method to include your own processing on what to do when pysmb fails to setup the NetBIOS session with the remote server. Usually, this is due to a wrong *remote_name* parameter in the constructor. diff --git a/sphinx/source/index.rst b/sphinx/source/index.rst index 7638fcd..b872dec 100644 --- a/sphinx/source/index.rst +++ b/sphinx/source/index.rst @@ -1,16 +1,11 @@ -.. pysmb documentation master file, created by - sphinx-quickstart on Sun Dec 18 15:54:40 2011. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - Welcome to pysmb's documentation! ================================= pysmb is a pure Python implementation of the client-side SMB/CIFS protocol (SMB1 and SMB2) which is the underlying protocol that facilitates file sharing and printing between Windows machines, as well as with Linux machines via the Samba server application. -pysmb is developed in Python 2.4.6, Python 2.7.1 and Python 3.2.3 and has been tested against shared folders on Windows XP SP3, Windows Vista, Windows 7 and Samba 3.x. +pysmb is developed in Python 2.7.x and Python 3.8.x and has been tested against shared folders on Windows 7, Windows 10 and Samba 4.x. -The latest version of pysmb is always available at the pysmb project page at `miketeo.net `_. +The latest version of pysmb is always available at the pysmb project page at `miketeo.net `_. License ------- @@ -90,6 +85,8 @@ As a software developer who is looking to modify pysmb so that you can integrate it to other network frameworks: * Read :doc:`extending` +If you are upgrading from older pysmb versions: + * Read :doc:`upgrading` Indices and tables @@ -101,6 +98,7 @@ api/* extending + upgrading * :ref:`genindex` * :ref:`search` diff --git a/sphinx/source/upgrading.rst b/sphinx/source/upgrading.rst new file mode 100644 index 0000000..8d5e7cd --- /dev/null +++ b/sphinx/source/upgrading.rst @@ -0,0 +1,63 @@ +Upgrading from older pysmb versions +==================================== + +This page documents the improvements and changes to the API that could be incompatible with previous releases. + +pysmb 1.2.0 +----------- +- Add new `delete_matching_folders` parameter to `deleteFiles()` method in SMBProtocolFactory and SMBConnection + class to support deletion of sub-folders. If you are passing timeout parameter to the `deleteFiles()` method + in your application, please switch to using named parameter for timeout. + +pysmb 1.1.28 +------------ +- SharedFile instances returned from the `listPath()` method now has a new property + `file_id` attribute which represents the file reference number given by the remote SMB server. + +pysmb 1.1.26 +------------ +- SMBConnection class can now be used as a context manager + +pysmb 1.1.25 +------------ +- SharedFile class has a new property `isNormal` which will be True if the file is a + 'normal' file. 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 in SMBProtocolFactory and SMBConnection class will now include + 'normal' files by default if you do not specify the `search` parameter. + +pysmb 1.1.20 +------------ +- A new method `getSecurity()` was added to SMBConnection and SMBProtocolFactory class. + +pysmb 1.1.15 +------------ +- Add new `truncate` parameter to `storeFileFromOffset()` in SMBProtocolFactory and SMBConnection + class to support truncation of the file before writing. If you are passing timeout parameter + to the `storeFileFromOffset()` method in your application, please switch to using named parameter for timeout. + +pysmb 1.1.11 +------------ +- A new method `storeFileFromOffset()` was added to SMBConnection and SMBProtocolFactory class. + +pysmb 1.1.10 +------------ +- A new method `getAttributes()` was added to SMBConnection and SMBProtocolFactory class +- SharedFile class has a new property `isReadOnly` to indicate the file is read-only on the remote filesystem. + +pysmb 1.1.2 +----------- +- `queryIPForName()` method in nmb.NetBIOS and nmb.NBNSProtocol class will now return only the server machine name and ignore workgroup names. + +pysmb 1.0.3 +----------- +- Two new methods were added to NBNSProtocol class: `queryIPForName()` and `NetBIOS.queryIPForName()` + to support querying for a machine's NetBIOS name at the given IP address. +- A new method `retrieveFileFromOffset()` was added to SMBProtocolFactory and SMBConnection + to support finer control of file retrieval operation. + +pysmb 1.0.0 +----------- +pysmb was completely rewritten in version 1.0.0. +If you are upgrading from pysmb 0.x, you most likely have to rewrite your application for the new 1.x API.