diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efb5642 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +*~ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea \ No newline at end of file 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 b/MANIFEST new file mode 100644 index 0000000..172ef7f --- /dev/null +++ b/MANIFEST @@ -0,0 +1,134 @@ +CHANGELOG +LICENSE +README.txt +setup.py +docs/doctrees/environment.pickle +docs/doctrees/extending.doctree +docs/doctrees/index.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/html/.buildinfo +docs/html/extending.html +docs/html/genindex.html +docs/html/index.html +docs/html/objects.inv +docs/html/search.html +docs/html/searchindex.js +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/smb_structs.html +docs/html/_sources/extending.txt +docs/html/_sources/index.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/_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 +nmb/NetBIOS.py +nmb/NetBIOSProtocol.py +nmb/__init__.py +nmb/base.py +nmb/nmb_constants.py +nmb/nmb_structs.py +nmb/utils.py +smb/SMBConnection.py +smb/SMBHandler.py +smb/SMBProtocol.py +smb/__init__.py +smb/base.py +smb/ntlm.py +smb/securityblob.py +smb/smb_constants.py +smb/smb_structs.py +smb/utils/README.txt +smb/utils/U32.py +smb/utils/__init__.py +smb/utils/md4.py +smb/utils/pyDes.py +sphinx/Makefile +sphinx/make.bat +sphinx/source/conf.py +sphinx/source/extending.rst +sphinx/source/index.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 +tests/README_1st.txt +tests/__init__.py +tests/connection.ini +tests/smbtest.zip +tests/test_ntlm.py +tests/test_securityblob.py +tests/NetBIOSTests/__init__.py +tests/NetBIOSTests/test_queryname.py +tests/NetBIOSTwistedTests/__init__.py +tests/NetBIOSTwistedTests/test_queryname.py +tests/SMBConnectionTests/__init__.py +tests/SMBConnectionTests/test_SMBHandler.py +tests/SMBConnectionTests/test_auth.py +tests/SMBConnectionTests/test_createdeletedirectory.py +tests/SMBConnectionTests/test_echo.py +tests/SMBConnectionTests/test_listpath.py +tests/SMBConnectionTests/test_listshares.py +tests/SMBConnectionTests/test_rename.py +tests/SMBConnectionTests/test_retrievefile.py +tests/SMBConnectionTests/test_storefile.py +tests/SMBConnectionTests/util.py +tests/SMBTwistedTests/__init__.py +tests/SMBTwistedTests/test_auth.py +tests/SMBTwistedTests/test_createdeletedirectory.py +tests/SMBTwistedTests/test_echo.py +tests/SMBTwistedTests/test_listpath.py +tests/SMBTwistedTests/test_listshares.py +tests/SMBTwistedTests/test_rename.py +tests/SMBTwistedTests/test_retrievefile.py +tests/SMBTwistedTests/test_storefile.py +tests/SMBTwistedTests/util.py +tests/SupportFiles/binary.dat 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 deleted file mode 100644 index ba886a1..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,26 +0,0 @@ -Metadata-Version: 1.1 -Name: pysmb -Version: 1.1.19 -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/README.md b/README.md new file mode 100644 index 0000000..9c23246 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +pysmb +===== + +pysmb is an experimental SMB/CIFS library written in Python. It implements the client-side SMB/CIFS protocol (SMB1 and SMB2) which allows your Python application to access and transfer files to/from SMB/CIFS shared folders like your Windows file sharing and Samba folders. + +* Primary Project Website: https://miketeo.net/blog/projects/pysmb +* Documentation: http://pysmb.readthedocs.io/ +* Issue Tracker: Please use the [issue tracker on github](https://github.com/miketeo/pysmb/issues). 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..036c6fa 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. @@ -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..29d414f 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 @@ -2634,18 +2985,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): @@ -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_1st.txt b/python2/tests/README_1st.txt index aa06a7c..7e918bf 100644 --- a/python2/tests/README_1st.txt +++ b/python2/tests/README_1st.txt @@ -19,14 +19,14 @@ 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. +3. Download smbtest.zip from https://miketeo.net/files/Projects/pysmb/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 +5. Run the Unit Tests in the python2 folder +Just run: nosetests -v tests +or selectively: nosetests -v tests/SMBConnectionTests 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/smb/SMBConnection.py b/python3/smb/SMBConnection.py index 172d203..dbfab3b 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. @@ -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..f88632f 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 @@ -2630,18 +2979,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): @@ -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..6b361ec 100644 --- a/python3/tests/DirectSMBConnectionTests/test_auth.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py b/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py index 814d89d..9356829 100644 --- a/python3/tests/DirectSMBConnectionTests/test_createdeletedirectory.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_echo.py b/python3/tests/DirectSMBConnectionTests/test_echo.py index c81675d..8159429 100644 --- a/python3/tests/DirectSMBConnectionTests/test_echo.py +++ b/python3/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(): diff --git a/python3/tests/DirectSMBConnectionTests/test_listpath.py b/python3/tests/DirectSMBConnectionTests/test_listpath.py index c3b08a7..8ae2f41 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listpath.py +++ b/python3/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 ( 'Test Folder', True ) in filenames assert ( '子文件夹', 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(list(filenames))==999 + @with_setup(setup_func_SMB2, teardown_func) def test_listPath_SMB2(): global conn @@ -64,3 +72,87 @@ assert ( 'Test File.txt', False ) in filenames assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', 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(list(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(list(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(list(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(list(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(list(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 = 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, 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, 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, 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..c728d74 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listshares.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py b/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py index 551581f..4f29456 100644 --- a/python3/tests/DirectSMBConnectionTests/test_listsnapshots.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_rename.py b/python3/tests/DirectSMBConnectionTests/test_rename.py index cb6d880..cb2e8f4 100644 --- a/python3/tests/DirectSMBConnectionTests/test_rename.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_retrievefile.py b/python3/tests/DirectSMBConnectionTests/test_retrievefile.py index e49ab8f..fcea18a 100644 --- a/python3/tests/DirectSMBConnectionTests/test_retrievefile.py +++ b/python3/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/python3/tests/DirectSMBConnectionTests/test_storefile.py b/python3/tests/DirectSMBConnectionTests/test_storefile.py index 80c804c..bb98b12 100644 --- a/python3/tests/DirectSMBConnectionTests/test_storefile.py +++ b/python3/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/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/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_1st.txt b/python3/tests/README_1st.txt new file mode 100644 index 0000000..82c2ac8 --- /dev/null +++ b/python3/tests/README_1st.txt @@ -0,0 +1,26 @@ + +Steps to Follow to Run the Unit Tests +===================================== + +1. 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/ + +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. Download smbtest.zip from https://miketeo.net/files/Projects/pysmb/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 in the python2 folder +Just run: nosetests3 -v tests +or selectively: nosetests3 -v tests/SMBConnectionTests diff --git a/python3/tests/SMBConnectionTests/test_deletepattern.py b/python3/tests/SMBConnectionTests/test_deletepattern.py index a7d183d..711a209 100644 --- a/python3/tests/SMBConnectionTests/test_deletepattern.py +++ b/python3/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, BytesIO(b"0123456789")) - - results = conn.listPath('smbtest', path) - filenames = list(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, 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 +58,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 +70,80 @@ 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) + 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, 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, 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)) +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 +154,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 +166,55 @@ 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, 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_listpath.py b/python3/tests/SMBConnectionTests/test_listpath.py index c3b08a7..4438891 100644 --- a/python3/tests/SMBConnectionTests/test_listpath.py +++ b/python3/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 ( 'Test Folder', True ) in filenames assert ( '子文件夹', 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(list(filenames))==999 + @with_setup(setup_func_SMB2, teardown_func) def test_listPath_SMB2(): global conn @@ -64,3 +72,98 @@ assert ( 'Test File.txt', False ) in filenames assert ( 'Test Folder', True ) in filenames assert ( '子文件夹', 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(list(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(list(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(list(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(list(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(list(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 = 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, 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, 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, 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, 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/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.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..f08a2c4 100644 --- a/sphinx/source/conf.py +++ b/sphinx/source/conf.py @@ -42,16 +42,16 @@ # General information about the project. project = u'pysmb' -copyright = u'2001-2015, Michael Teo http://miketeo.net/' +copyright = u'2001-2020, 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/index.rst b/sphinx/source/index.rst index 7638fcd..e3eb1ac 100644 --- a/sphinx/source/index.rst +++ b/sphinx/source/index.rst @@ -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/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. diff --git a/utils/ScanNetworkForSMB.py b/utils/ScanNetworkForSMB.py new file mode 100644 index 0000000..02c07dd --- /dev/null +++ b/utils/ScanNetworkForSMB.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# +# ScanNetworkForSMB.py - Script for scanning network for open SMB/CIFS services +# Copyright (C) 2012 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 +# use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# +# 3. This notice cannot be removed or altered from any source distribution. +# + +import sys, select, socket, random, string, time +from nmb import base + + +class NonBlockingNetBIOS(base.NBNS): + + def __init__(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + + self.pendings = set() + self.pending_count = 0 + + def write(self, data, ip, port): + assert self.sock, 'Socket is already closed' + self.sock.sendto(data, ( ip, port )) + + def queryIPForName(self, ip): + assert self.sock, 'Socket is already closed' + + trn_id = random.randint(1, 0xFFFF) + data = self.prepareNetNameQuery(trn_id) + self.write(data, ip, 137) + self.pendings.add(ip) + self.pending_count += 1 + + def queryResult(self, ip, results): + results = filter(lambda s: s and s[0] in string.printable, results) + if results: + print ip.rjust(16), '-->', ' '.join(results) + + def poll(self, timeout = 0): + end_time = time.time() + timeout + while self.pending_count > 0 and (timeout == 0 or time.time() < end_time): + t = max(0, end_time - time.time()) + try: + ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], t) + if not ready: + return None + + data, ( ip, port ) = self.sock.recvfrom(0xFFFF) + _, ret = self.decodeIPQueryPacket(data) + + try: + self.pendings.remove(ip) + self.pending_count -= 1 + + self.queryResult(ip, set(ret)) + except KeyError: pass + except select.error, ex: + if type(ex) is types.TupleType: + if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN: + raise ex + else: + raise ex + + +# Originally from http://snipplr.com/view/14807/ +def DottedIPToInt(dotted_ip): + exp = 3 + intip = 0 + for quad in dotted_ip.split('.'): + intip = intip + (int(quad) * (256 ** exp)) + exp = exp - 1 + return(intip) + +def IntToDottedIP( intip ): + octet = '' + for exp in [3,2,1,0]: + octet = octet + str(intip / ( 256 ** exp )) + "." + intip = intip % ( 256 ** exp ) + return(octet.rstrip('.')) + +def main(): + if len(sys.argv) > 2: + start_ip = DottedIPToInt(sys.argv[1]) + end_ip = DottedIPToInt(sys.argv[2]) + elif len(sys.argv) == 2: + start_ip = DottedIPToInt(sys.argv[1]) + end_ip = start_ip + else: + print 'ScanNetworkForSMB - Script for scanning network for open SMB/CIFS services' + print 'Error: missing IP arguments' + print 'Usage:', sys.argv[0], 'start-IP-address [end-IP-address]' + print + return + + print 'Beginning scanning %d IP addresses...' % ( end_ip-start_ip+1, ) + print + + ns = NonBlockingNetBIOS() + for ip in range(start_ip, end_ip + 1): + ns.queryIPForName(IntToDottedIP(ip)) + ns.poll() + + if ns.pending_count > 0: + ns.poll(10) + print + print 'Query timeout. No replies from %d IP addresses' % ns.pending_count + + +if __name__ == '__main__': + main() diff --git a/utils/recursiveDelete.py b/utils/recursiveDelete.py new file mode 100644 index 0000000..75ed377 --- /dev/null +++ b/utils/recursiveDelete.py @@ -0,0 +1,38 @@ +#!/usr/bin/python2 +# +# Simple script to demonstrate how to delete all files/sub-folders in the shared folder +# +from smb.SMBConnection import SMBConnection + +dry_run = True # Set to True to test if all files/folders can be "walked". Set to False to perform the deletion. +userID = 'myuser' +password = 'mypassword' +client_machine_name = 'testclient' # Usually safe to use 'testclient' +server_name = 'MYSERVER' # Must match the NetBIOS name of the remote server +server_ip = '192.168.1.10' # Must point to the correct IP address +domain_name = '' # Safe to leave blank, or fill in the domain used for your remote server +shared_folder = 'smbtest' # Set to the shared folder name + +conn = SMBConnection(userID, password, client_machine_name, server_name, domain=domain_name, use_ntlm_v2=True, is_direct_tcp=True) +conn.connect(server_ip, 445) + +def walk_path(path): + print 'Walking path', path + for p in conn.listPath(shared_folder, path): + if p.filename!='.' and p.filename!='..': + parentPath = path + if not parentPath.endswith('/'): + parentPath += '/' + + if p.isDirectory: + walk_path(parentPath+p.filename) + print 'Deleting folder (%s) in %s' % ( p.filename, path ) + if not dry_run: + conn.deleteDirectory(shared_folder, parentPath+p.filename) + else: + print 'Deleting file (%s) in %s' % ( p.filename, path ) + if not dry_run: + conn.deleteFiles(shared_folder, parentPath+p.filename) + +# Start and delete everything at shared folder root +walk_path('/')