Example #1
0
    async def _tree_connect(
            self,
            share_name: str,
            server_address: str = '*') -> Tuple[int, ShareType]:

        if self.connection.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        tree_connect_response: TreeConnectResponse = await self.connection._obtain_response(
            request_message=TreeConnectRequest210(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_TREE_CONNECT,
                    session_id=self.session_id,
                ),
                path=f'\\\\{server_address}\\{share_name}'),
            sign_key=self.session_key)

        tree_connect_object = TreeConnectObject(
            tree_connect_id=tree_connect_response.header.tree_id,
            session=self,
            is_dfs_share=tree_connect_response.share_capabilities.dfs,
            is_ca_share=tree_connect_response.share_capabilities.
            continuous_availability,
            share_name=share_name)

        self.tree_connect_id_to_tree_connect_object[
            tree_connect_response.header.tree_id] = tree_connect_object
        self.share_name_to_tree_connect_object[
            share_name] = tree_connect_object

        return tree_connect_response.header.tree_id, tree_connect_response.share_type
Example #2
0
        async def read_chunks():
            num_bytes_remaining = file_size
            offset = 0
            while num_bytes_remaining != 0:
                num_bytes_to_read = min(
                    num_bytes_remaining,
                    self.connection.negotiated_details.max_read_size)
                read_response: ReadResponse = await self.connection._obtain_response(
                    request_message=ReadRequest210(
                        header=SMB210SyncRequestHeader(
                            command=SMBv2Command.SMB2_READ,
                            session_id=self.session_id,
                            tree_id=tree_id,
                            credit_charge=calculate_credit_charge(
                                variable_payload_size=0,
                                expected_maximum_response_size=file_size)),
                        padding=Header.STRUCTURE_SIZE +
                        (ReadResponse.STRUCTURE_SIZE - 1),
                        length=num_bytes_to_read,
                        offset=offset,
                        file_id=file_id,
                        minimum_count=0,
                        remaining_bytes=0),
                    sign_key=self.session_key)

                yield read_response.buffer

                num_bytes_remaining -= num_bytes_to_read
                offset += num_bytes_to_read
Example #3
0
        async def read_chunks():
            data_remains = True
            offset = 0
            while data_remains:
                read_response: ReadResponse = await self._obtain_response(
                    request_message=ReadRequest210(
                        header=SMB210SyncRequestHeader(
                            command=SMBv2Command.SMB2_READ,
                            session_id=session.session_id,
                            tree_id=tree_id,
                            credit_charge=calculate_credit_charge(
                                variable_payload_size=0,
                                expected_maximum_response_size=file_size)),
                        padding=Header.STRUCTURE_SIZE +
                        (ReadResponse.STRUCTURE_SIZE - 1),
                        length=file_size,
                        offset=offset,
                        file_id=file_id,
                        minimum_count=0,
                        remaining_bytes=0))

                yield read_response.buffer

                data_remains = read_response.data_remaining_length != 0
                offset += read_response.data_length
Example #4
0
    async def _tree_connect(
        self,
        share_name: str,
        session: SMBv2Session,
        server_address: Optional[Union[str, IPv4Address, IPv6Address]] = None
    ) -> Tuple[int, ShareType]:

        # TODO: "If ServerName is an empty string, the server MUST set it as "*" to indicate that the local server name
        #  used." -- Does this mean that I don't need a server address!?

        tree_connect_response: TreeConnectResponse = await self._obtain_response(
            request_message=TreeConnectRequest210(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_TREE_CONNECT,
                    session_id=session.session_id,
                ),
                path=f'\\\\{server_address or self._host_address}\\{share_name}'
            ))

        tree_connect_object = TreeConnectObject(
            tree_connect_id=tree_connect_response.header.tree_id,
            session=session,
            is_dfs_share=tree_connect_response.share_capabilities.dfs,
            is_ca_share=tree_connect_response.share_capabilities.
            continuous_availability,
            share_name=share_name)

        session.tree_connect_id_to_tree_connect_object[
            tree_connect_response.header.tree_id] = tree_connect_object
        session.share_name_to_tree_connect_object[
            share_name] = tree_connect_object

        return tree_connect_response.header.tree_id, tree_connect_response.share_type
Example #5
0
    async def query_directory(
        self,
        file_id: FileId,
        file_information_class: FileInformationClass,
        query_directory_flag: QueryDirectoryFlag,
        session: SMBv2Session,
        tree_id: int,
        file_name_pattern: str = '',
        file_index: int = 0,
        output_buffer_length: int = 256_000
    ) -> List[Union[FileDirectoryInformation, FileIdFullDirectoryInformation]]:
        """
        Obtain information about a directory in an SMB share.

        :param file_id: An identifier for the directory about which to obtain information.
        :param file_information_class: A specification of the type of information to obtain.
        :param query_directory_flag: A flag indicating how the operation is to be processed.
        :param session: An SMB session with access to the directory.
        :param tree_id: The tree id of the SMB share that stores the directory.
        :param file_name_pattern: A search pattern specifying which entries in the the directory to retrieve information
            about.
        :param file_index: The byte offset within the directory, indicating the position at which to resume the
            enumeration.
        :param output_buffer_length: The maximum number of bytes the server is allowed to return in the response.
        :return: A collection of information entries about the content of the directory.
        """

        if self.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        query_directory_response: QueryDirectoryResponse = await self._obtain_response(
            request_message=QueryDirectoryRequest(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_QUERY_DIRECTORY,
                    session_id=session.session_id,
                    tree_id=tree_id,
                    # TODO: Consider this value.
                    credit_charge=64),
                file_information_class=file_information_class,
                flags=query_directory_flag,
                file_id=file_id,
                file_name=file_name_pattern,
                file_index=file_index,
                output_buffer_length=output_buffer_length))

        if file_information_class is FileInformationClass.FileDirectoryInformation:
            return query_directory_response.file_directory_information()
        elif file_information_class is FileInformationClass.FileIdFullDirectoryInformation:
            return query_directory_response.file_id_full_directory_information(
            )
        else:
            raise NotImplementedError
Example #6
0
    async def logoff(self) -> LogoffResponse:
        """
        Terminate a session.

        :return: A `LOGOFF` response.
        """

        if self.connection.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        return await self.connection._obtain_response(
            request_message=LogoffRequest(header=SMB210SyncRequestHeader(
                command=SMBv2Command.SMB2_LOGOFF, session_id=self.session_id)),
            sign_key=self.session_key)
Example #7
0
    async def logoff(self, session: SMBv2Session) -> None:
        """
        Terminate a session.

        :param session: The session to be terminated.
        :return: None
        """

        if self.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        await self._obtain_response(request_message=LogoffRequest(
            header=SMB210SyncRequestHeader(command=SMBv2Command.SMB2_LOGOFF,
                                           session_id=session.session_id)))
Example #8
0
    async def _create(
            self,
            path: Union[str, PureWindowsPath],
            tree_id: int,
            requested_oplock_level: OplockLevel = OplockLevel.
        SMB2_OPLOCK_LEVEL_BATCH,
            impersonation_level: ImpersonationLevel = ImpersonationLevel.
        IMPERSONATION,
            desired_access: Union[
                FilePipePrinterAccessMask,
                DirectoryAccessMask] = FilePipePrinterAccessMask(
                    file_read_data=True,
                    file_read_ea=True,
                    file_read_attributes=True),
            file_attributes: FileAttributes = FileAttributes(normal=True),
            share_access: ShareAccess = ShareAccess(read=True),
            create_disposition: CreateDisposition = CreateDisposition.
        FILE_OPEN,
            create_options: CreateOptions = CreateOptions(
                non_directory_file=True),
            create_context_list: CreateContextList = None) -> CreateResponse:
        create_context_list = create_context_list if create_context_list is not None else CreateContextList(
        )

        if self.connection.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        create_response: CreateResponse = await self.connection._obtain_response(
            request_message=CreateRequest(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_CREATE,
                    session_id=self.session_id,
                    tree_id=tree_id,
                ),
                requested_oplock_level=requested_oplock_level,
                impersonation_level=impersonation_level,
                desired_access=desired_access,
                file_attributes=file_attributes,
                share_access=share_access,
                create_disposition=create_disposition,
                create_options=create_options,
                name=str(path),
                create_context_list=create_context_list),
            sign_key=self.session_key)

        # TODO: I need to add stuff to some connection table, don't I?
        # TODO: Consider what to return from this function. There is a lot of information in the response.
        return create_response
Example #9
0
    async def tree_disconnect(self, session: SMBv2Session, tree_id: int):
        """
        Request that a tree connect is disconnected.

        :param session: An SMB session that has access to the tree connect.
        :param tree_id: The ID of the tree connect to be disconnected.
        :return: None
        """

        if self.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        await self._obtain_response(request_message=TreeDisconnectRequest(
            header=SMB210SyncRequestHeader(
                command=SMBv2Command.SMB2_TREE_DISCONNECT,
                session_id=session.session_id,
                tree_id=tree_id)))
Example #10
0
    async def tree_disconnect(self, tree_id: int) -> TreeDisconnectResponse:
        """
        Request that a tree connect is disconnected.

        :param tree_id: The ID of the tree connect to be disconnected.
        :return: A `TREE_DISCONNECT` resposne.
        """

        if self.connection.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        return await self.connection._obtain_response(
            request_message=TreeDisconnectRequest(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_TREE_DISCONNECT,
                    session_id=self.session_id,
                    tree_id=tree_id)),
            sign_key=self.session_key)
Example #11
0
    async def write(
        self,
        write_data: bytes,
        file_id: FileId,
        session: SMBv2Session,
        tree_id: int,
        offset: int = 0,
        remaining_bytes: int = 0,
        flags: WriteFlag = WriteFlag()) -> int:
        """
        Write data to a file in an SMB share.

        :param write_data: The data to be written.
        :param file_id: An identifier of the file whose data is to be written.
        :param session: An SMB session with access to the file.
        :param tree_id: The tree id of the SMB share that stores the file.
        :param offset: The offset, in bytes, of where to write the data in the destination file.
        :param remaining_bytes: The number of subsequent bytes the client intends to write to the file after this
            operation completes. Not binding.
        :param flags: Flags indicating how to process the operation.
        :return: The number of bytes written.
        """

        # TODO: Support more dialects.
        if self.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        write_response: WriteResponse = await self._obtain_response(
            request_message=WriteRequest210(header=SMB210SyncRequestHeader(
                command=SMBv2Command.SMB2_WRITE,
                session_id=session.session_id,
                tree_id=tree_id,
                credit_charge=calculate_credit_charge(
                    variable_payload_size=0,
                    expected_maximum_response_size=Header.STRUCTURE_SIZE +
                    WriteResponse.STRUCTURE_SIZE)),
                                            write_data=write_data,
                                            offset=offset,
                                            file_id=file_id,
                                            remaining_bytes=remaining_bytes,
                                            flags=flags))

        return write_response.count
Example #12
0
    async def close(self, tree_id: int, file_id: FileId) -> CloseResponse:
        """
        Close an instance of a file opened with a CREATE request.

        :param tree_id: The tree id of the share in which the opened file resides.
        :param file_id: The file id of the file instance to be closed.
        :return: A `CLOSE` response.
        """

        if self.connection.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        return await self.connection._obtain_response(
            request_message=CloseRequest(header=SMB210SyncRequestHeader(
                command=SMBv2Command.SMB2_CLOSE,
                session_id=self.session_id,
                tree_id=tree_id),
                                         flags=CloseFlag(),
                                         file_id=file_id),
            sign_key=self.session_key)
Example #13
0
    async def change_notify(
            self,
            file_id: FileId,
            session: SMBV2Session,
            tree_id: int,
            completion_filter_flag: Optional[CompletionFilterFlag] = None,
            watch_tree: bool = False) -> Awaitable[ChangeNotifyResponse]:
        """
        Monitor a directory in an SMB share for changes and notify.

        Only one notification is sent per change notify request. The notification is sent asynchronously.

        :param file_id: An identifier for the directory to be monitored for changes.
        :param session: An SMB session with access to the directory to be monitored.
        :param tree_id: The tree ID of the share that stores the directory to be monitored.
        :param completion_filter_flag: A flag that specifies which type of changes to notify about.
        :param watch_tree: Whether to monitor the subdirectories of the directory.
        :return: A `Future` object that resolves to a `ChangeNotifyResponse` containing a notification.
        """

        if completion_filter_flag is None:
            completion_filter_flag = CompletionFilterFlag()
            completion_filter_flag.set_all()

        if self.negotiated_details.dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        return await self._obtain_response(
            request_message=ChangeNotifyRequest(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_CHANGE_NOTIFY,
                    session_id=session.session_id,
                    tree_id=tree_id,
                    # TODO: Arbitrary number. Reconsider.
                    credit_charge=64),
                flags=ChangeNotifyFlag(watch_tree=watch_tree),
                file_id=file_id,
                completion_filter=completion_filter_flag),
            await_async_response=False)
Example #14
0
    async def negotiate(self) -> None:
        """
        Negotiate the SMB dialect to be used.

        :return: None
        """

        # TODO: In future, I want to support more dialects.
        negotiate_response: NegotiateResponse = await self._obtain_response(
            request_message=NegotiateRequest(header=SMB210SyncRequestHeader(
                command=SMBv2Command.SMB2_NEGOTIATE),
                                             dialects=(PREFERRED_DIALECT, ),
                                             client_guid=CLIENT_GUID,
                                             security_mode=SECURITY_MODE))

        negotiated_details_base_kwargs = dict(
            dialect=negotiate_response.dialect_revision,
            require_signing=(bool(negotiate_response.security_mode is
                                  SecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED)
                             or REQUIRE_MESSAGE_SIGNING),
            server_guid=negotiate_response.server_guid,
            max_transact_size=negotiate_response.max_transact_size,
            max_read_size=negotiate_response.max_read_size,
            max_write_size=negotiate_response.max_write_size)

        negotiated_details_base_kwargs_2 = dict(
            supports_file_leasing=negotiate_response.capabilities.leasing,
            supports_multi_credit=negotiate_response.capabilities.large_mtu)

        negotiated_details_base_kwargs_3 = dict(
            supports_directory_leasing=negotiate_response.capabilities.
            directory_leasing,
            supports_multi_channel=negotiate_response.capabilities.
            multi_channel,
            supports_persistent_handles=negotiate_response.capabilities.
            persistent_handles,
            supports_encryption=negotiate_response.capabilities.encryption)

        if isinstance(negotiate_response, SMB202NegotiateResponse):
            self.negotiated_details = SMB202NegotiatedDetails(
                **negotiated_details_base_kwargs)
        elif isinstance(negotiate_response, SMB210NegotiateResponse):
            self.negotiated_details = SMB210NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2)
        elif isinstance(negotiate_response, SMB300NegotiateResponse):
            self.negotiated_details = SMB300NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3)
        elif isinstance(negotiate_response, SMB302NegotiateResponse):
            self.negotiated_details = SMB302NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3)
        elif isinstance(negotiate_response, SMB311NegotiateResponse):

            negotiated_context_map = dict(
                preauth_integrity_hash_id=None,
                # TODO: Not sure from where I would get this.
                preauth_integrity_hash_value=None,
                cipher_id=None,
                compression_ids=None)

            for negotiate_context in negotiate_response.negotiate_context_list:
                if isinstance(negotiate_context,
                              PreauthIntegrityCapabilitiesContext):
                    negotiated_context_map['preauth_integrity_hash_id'] = next(
                        negotiate_context.hash_algorithms)
                elif isinstance(negotiate_context,
                                EncryptionCapabilitiesContext):
                    negotiated_context_map['cipher_id'] = next(
                        negotiate_context.ciphers)
                elif isinstance(negotiate_context,
                                CompressionCapabilitiesContext):
                    negotiated_context_map['compression_ids'] = set(
                        negotiate_context.compression_algorithms)
                elif isinstance(negotiate_context,
                                NetnameNegotiateContextIdContext):
                    # TODO: Do something with this.
                    ...
                else:
                    # TODO: Use a proper exception.
                    raise ValueError

            self.negotiated_details = SMB311NegotiateDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3, **negotiated_context_map)
        else:
            # TODO: Use proper exception.
            raise ValueError
Example #15
0
    async def negotiate(
        self,
        preferred_dialect: Dialect = Dialect.SMB_2_1,
        security_mode: SecurityMode = SecurityMode(signing_required=True)
    ) -> None:
        """
        Negotiate the SMB configuration to be used.

        :return: None
        """

        if preferred_dialect is not Dialect.SMB_2_1:
            raise NotImplementedError

        # TODO: In future, I want to support more dialects.
        negotiate_response: NegotiateResponse = await self._obtain_response(
            request_message=NegotiateRequest(
                header=SMB210SyncRequestHeader(
                    command=SMBv2Command.SMB2_NEGOTIATE
                ),
                dialects=(preferred_dialect,),
                client_guid=self.client_guid,
                security_mode=security_mode
            )
        )

        # TODO: Check if the server is also accepting signing?

        negotiated_details_base_kwargs = dict(
            dialect=negotiate_response.dialect_revision,
            require_signing=negotiate_response.security_mode.signing_required,
            server_guid=negotiate_response.server_guid,
            max_transact_size=negotiate_response.max_transact_size,
            max_read_size=negotiate_response.max_read_size,
            max_write_size=negotiate_response.max_write_size
        )

        negotiated_details_base_kwargs_2 = dict(
            supports_file_leasing=negotiate_response.capabilities.leasing,
            supports_multi_credit=negotiate_response.capabilities.large_mtu
        )

        negotiated_details_base_kwargs_3 = dict(
            supports_directory_leasing=negotiate_response.capabilities.directory_leasing,
            supports_multi_channel=negotiate_response.capabilities.multi_channel,
            supports_persistent_handles=negotiate_response.capabilities.persistent_handles,
            supports_encryption=negotiate_response.capabilities.encryption
        )

        if isinstance(negotiate_response, SMB202NegotiateResponse):
            self.negotiated_details = SMB202NegotiatedDetails(**negotiated_details_base_kwargs)
        elif isinstance(negotiate_response, SMB210NegotiateResponse):
            self.negotiated_details = SMB210NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2
            )
        elif isinstance(negotiate_response, SMB300NegotiateResponse):
            self.negotiated_details = SMB300NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3
            )
        elif isinstance(negotiate_response, SMB302NegotiateResponse):
            self.negotiated_details = SMB302NegotiatedDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3
            )
        elif isinstance(negotiate_response, SMB311NegotiateResponse):

            negotiated_context_map = dict(
                preauth_integrity_hash_id=None,
                # TODO: Not sure from where I would get this.
                preauth_integrity_hash_value=None,
                cipher_id=None,
                compression_ids=None
            )

            for negotiate_context in negotiate_response.negotiate_context_list:
                if isinstance(negotiate_context, PreauthIntegrityCapabilitiesContext):
                    negotiated_context_map['preauth_integrity_hash_id'] = next(negotiate_context.hash_algorithms)
                elif isinstance(negotiate_context, EncryptionCapabilitiesContext):
                    negotiated_context_map['cipher_id'] = next(negotiate_context.ciphers)
                elif isinstance(negotiate_context, CompressionCapabilitiesContext):
                    negotiated_context_map['compression_ids'] = set(negotiate_context.compression_algorithms)
                elif isinstance(negotiate_context, NetnameNegotiateContextIdContext):
                    # TODO: Do something with this.
                    ...
                else:
                    # TODO: Use a proper exception.
                    raise ValueError

            self.negotiated_details = SMB311NegotiateDetails(
                **negotiated_details_base_kwargs,
                **negotiated_details_base_kwargs_2,
                **negotiated_details_base_kwargs_3,
                **negotiated_context_map
            )
        else:
            # TODO: Use proper exception.
            raise ValueError