diff --git a/.travis.yml b/.travis.yml index 5fee3259..7774ec42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,12 +21,7 @@ env: - SMB_PORT: 445 install: -- | - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then - export SMB_SKIP="True"; - else - docker run -d -p $SMB_PORT:445 -v $(pwd)/build-scripts:/app -w /app -e SMB_USER=$SMB_USER -e SMB_PASSWORD=$SMB_PASSWORD -e SMB_SHARE=$SMB_SHARE centos:7 /bin/bash /app/setup_samba.sh; - fi +- docker run -d -p $SMB_PORT:445 -v $(pwd)/build-scripts:/app -w /app -e SMB_USER=$SMB_USER -e SMB_PASSWORD=$SMB_PASSWORD -e SMB_SHARE=$SMB_SHARE centos:7 /bin/bash /app/setup_samba.sh; - pip install -U pip setuptools - pip install . - pip install -r requirements-test.txt diff --git a/smbprotocol/__init__.py b/smbprotocol/__init__.py index a1ceb597..e7b97fc0 100644 --- a/smbprotocol/__init__.py +++ b/smbprotocol/__init__.py @@ -10,4 +10,4 @@ def emit(self, record): logger = logging.getLogger(__name__) logger.addHandler(NullHandler()) -__version__ = '0.0.1.dev1' +__version__ = '0.0.1.dev2' diff --git a/smbprotocol/connection.py b/smbprotocol/connection.py index 0e2d854b..9ae03c65 100644 --- a/smbprotocol/connection.py +++ b/smbprotocol/connection.py @@ -170,6 +170,7 @@ def get_supported_ciphers(): pass try: aead.AESCCM(b"\x00" * 16) + supported_ciphers.append(Ciphers.AES_128_CCM) except UnsupportedAlgorithm: # pragma: no cover pass return supported_ciphers @@ -185,13 +186,16 @@ class NtStatus(object): """ STATUS_SUCCESS = 0x00000000 STATUS_PENDING = 0x00000103 + STATUS_BUFFER_OVERFLOW = 0x80000005 STATUS_EA_LIST_INCONSISTENT = 0x80000014 STATUS_STOPPED_ON_SYMLINK = 0x8000002D STATUS_INVALID_PARAMETER = 0xC000000D + STATUS_NO_SUCH_FILE = 0xC000000F STATUS_END_OF_FILE = 0xC0000011 STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 STATUS_ACCESS_DENIED = 0xC0000022 STATUS_BUFFER_TOO_SMALL = 0xC0000023 + STATUS_OBJECT_NAME_INVALID = 0xC0000033 STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034 STATUS_OBJECT_NAME_COLLISION = 0xC0000035 STATUS_OBJECT_PATH_INVALID = 0xC0000039 diff --git a/smbprotocol/open.py b/smbprotocol/open.py index 0711ab51..b0ff8edc 100644 --- a/smbprotocol/open.py +++ b/smbprotocol/open.py @@ -327,7 +327,7 @@ def __init__(self): )), ('name_length', IntField( size=2, - default=lambda s: len(s['buffer_path']) + default=lambda s: self._name_length(s) )), ('create_contexts_offset', IntField( size=4, @@ -340,7 +340,7 @@ def __init__(self): # Technically these are all under buffer but we split it to make # things easier ('buffer_path', BytesField( - size=lambda s: s['name_length'].get_value(), + size=lambda s: self._buffer_path_size(s), )), ('padding', BytesField( size=lambda s: self._padding_size(s), @@ -356,6 +356,10 @@ def __init__(self): ]) super(SMB2CreateRequest, self).__init__() + def _name_length(self, structure): + buffer_path = structure['buffer_path'].get_value() + return len(buffer_path) if buffer_path != b"\x00\x00" else 0 + def _create_contexts_offset(self, structure): if len(structure['buffer_contexts']) == 0: return 0 @@ -363,6 +367,10 @@ def _create_contexts_offset(self, structure): return structure['name_offset'].get_value() + \ len(structure['padding']) + len(structure['buffer_path']) + def _buffer_path_size(self, structure): + name_length = structure['name_length'].get_value() + return name_length if name_length != 0 else 2 + def _padding_size(self, structure): # no padding is needed if there are no contexts if structure['create_contexts_length'].get_value() == 0: @@ -962,7 +970,10 @@ def open(self, impersonation_level, desired_access, file_attributes, create['share_access'] = share_access create['create_disposition'] = create_disposition create['create_options'] = create_options - create['buffer_path'] = self.file_name.encode('utf-16-le') + if self.file_name == "": + create['buffer_path'] = b"\x00\x00" + else: + create['buffer_path'] = self.file_name.encode('utf-16-le') if create_contexts: create['buffer_contexts'] = smbprotocol.create_contexts.\ SMB2CreateContextRequest.pack_multiple(create_contexts) diff --git a/smbprotocol/transport.py b/smbprotocol/transport.py index a59b59b8..22eae9a1 100644 --- a/smbprotocol/transport.py +++ b/smbprotocol/transport.py @@ -1,3 +1,4 @@ +import errno import logging import socket import struct @@ -76,8 +77,7 @@ def send(self, request): try: sent = self._sock.send(data) except socket.error as err: - # errno: 35 == Resource temporarily unavailable, try again - if err.errno != 35: + if err.errno not in [errno.EAGAIN, errno.EWOULDBLOCK]: raise err data = data[sent:] @@ -101,11 +101,11 @@ def _recv(self, buffer): data = self._sock.recv(buffer - len(bytes)) bytes += data except socket.error as err: - # errno: 35 == Resource temporarily unavailable - if err.errno != 35: + if err.errno not in [errno.EAGAIN, errno.EWOULDBLOCK]: raise err - # we didn't get any bytes so return None + # this was the first request so return None elif bytes == b"": return None - # there is still data remaining so continue trying ot read + # we started getting data and there is still some remaining + # so try again return bytes diff --git a/tests/test_open.py b/tests/test_open.py index 4b7bc475..395fb1e3 100644 --- a/tests/test_open.py +++ b/tests/test_open.py @@ -1041,6 +1041,9 @@ def test_dialect_2_1_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_0(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -1084,6 +1087,9 @@ def test_dialect_3_0_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_2(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) @@ -1170,7 +1176,33 @@ def test_dialect_3_1_1(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.name == "nt", + reason="Sharing violation occurs when running with " + "Windows") + def test_open_root_directory(self, smb_real): + connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) + connection.connect(Dialects.SMB_3_1_1) + session = Session(connection, smb_real[0], smb_real[1]) + tree = TreeConnect(session, smb_real[5]) + dir_open = Open(tree, "") + try: + session.connect() + tree.connect() + + dir_open.open(ImpersonationLevel.Impersonation, + DirectoryAccessMask.MAXIMUM_ALLOWED, + FileAttributes.FILE_ATTRIBUTE_DIRECTORY, + 0, + CreateDisposition.FILE_OPEN_IF, + CreateOptions.FILE_DIRECTORY_FILE) + dir_open.close(get_attributes=False) + finally: + connection.disconnect(True) + # test more file operations here + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_create_directory(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -1215,6 +1247,9 @@ def test_create_directory(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_create_file_create_contexts(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -1267,7 +1302,7 @@ def test_create_file_create_contexts(self, smb_real): def test_create_read_write_from_file(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) - connection.connect(Dialects.SMB_3_0_2) + connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "file-read-write.txt") @@ -1289,6 +1324,9 @@ def test_create_read_write_from_file(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_flush_file(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) @@ -1363,6 +1401,9 @@ def test_close_file_get_attributes(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_read_file_unbuffered(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) @@ -1386,6 +1427,9 @@ def test_read_file_unbuffered(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_read_file_unbuffered_unsupported(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -1421,7 +1465,7 @@ def test_read_file_unbuffered_unsupported(self, smb_real): reason="write-through writes don't work on windows?") def test_write_file_write_through(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) - connection.connect(Dialects.SMB_3_0_2) + connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "file-read-write.txt") @@ -1479,7 +1523,7 @@ def test_write_file_write_through_unsupported(self, smb_real): reason="unbufferred writes don't work on windows?") def test_write_file_unbuffered(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) - connection.connect(Dialects.SMB_3_0_2) + connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "file-read-write.txt") @@ -1535,7 +1579,7 @@ def test_write_file_unbuffered_unsupported(self, smb_real): def test_query_directory(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) - connection.connect(Dialects.SMB_3_0_2) + connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "directory") @@ -1599,7 +1643,7 @@ def test_query_directory(self, smb_real): reason="flush in compound does't work on windows") def test_compounding_open_requests(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) - connection.connect(Dialects.SMB_3_0_2) + connection.connect() session = Session(connection, smb_real[0], smb_real[1]) tree = TreeConnect(session, smb_real[4]) open = Open(tree, "directory") @@ -1787,6 +1831,9 @@ def test_compounding_open_requests_unencrypted(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_close_file_already_closed(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) diff --git a/tests/test_session.py b/tests/test_session.py index 692f5fcf..af91faf0 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,3 +1,4 @@ +import os import uuid import pytest @@ -145,6 +146,9 @@ def test_dialect_2_1_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_0(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -168,6 +172,9 @@ def test_dialect_3_0_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_2(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) diff --git a/tests/test_tree.py b/tests/test_tree.py index 141c664e..b75cfd3c 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -1,3 +1,4 @@ +import os import uuid import pytest @@ -139,6 +140,9 @@ def test_dialect_2_1_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_0(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_0) @@ -155,6 +159,9 @@ def test_dialect_3_0_0(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_dialect_3_0_2(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) @@ -218,6 +225,9 @@ def test_dialect_3_encrypted_share(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_secure_negotiation_verification_failed(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) @@ -233,6 +243,9 @@ def test_secure_negotiation_verification_failed(self, smb_real): finally: connection.disconnect(True) + @pytest.mark.skipif(os.environ.get("TRAVIS_PYTHON_VERSION", "") == '2.6', + reason="Travis-CI Python 2.6 does not support AES CCM " + "required for Dialect 3.0.0 and 3.0.2 enc") def test_secure_ignore_negotiation_verification_failed(self, smb_real): connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3]) connection.connect(Dialects.SMB_3_0_2) diff --git a/tests/utils.py b/tests/utils.py index a34ea92e..53eb2876 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,9 +13,8 @@ def smb_real(): server = os.environ.get('SMB_SERVER', None) port = os.environ.get('SMB_PORT', None) share = os.environ.get('SMB_SHARE', None) - skip = os.environ.get('SMB_SKIP', "False") == "True" - if username and password and server and port and share and not skip: + if username and password and server and port and share: share = r"\\%s\%s" % (server, share) encrypted_share = "%s-encrypted" % share return username, password, server, int(port), share, encrypted_share