Esempio n. 1
0
    def create_service(self):
        # check if the service exists and delete it
        log.debug("Ensuring service is deleted before starting")
        self._service.delete()

        # copy across the PAExec payload to C:\Windows\
        smb_tree = TreeConnect(self.session,
                               r"\\%s\ADMIN$" % self.connection.server_name)
        log.info("Connecting to SMB Tree %s" % smb_tree.share_name)
        smb_tree.connect()
        paexec_file = Open(smb_tree, self._exe_file)
        log.debug("Creating open to PAExec file")
        paexec_file.open(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.FILE_WRITE_DATA,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)
        log.info("Creating PAExec executable at %s\\%s" %
                 (smb_tree.share_name, self._exe_file))
        for (data, o) in paexec_out_stream(self.connection.max_write_size):
            paexec_file.write(data, o)
        log.debug("Closing open to PAExec file")
        paexec_file.close(False)
        log.info("Disconnecting from SMB Tree %s" % smb_tree.share_name)
        smb_tree.disconnect()

        # create the PAExec service
        service_path = r'"%SystemRoot%\{0}" -service'.format(self._exe_file)
        log.info("Creating PAExec service %s" % self.service_name)
        self._service.create(service_path)
Esempio n. 2
0
    def session(self):
        server = os.environ.get('PYPSEXEC_SERVER', None)
        username = os.environ.get('PYPSEXEC_USERNAME', None)
        password = os.environ.get('PYPSEXEC_PASSWORD', None)

        if server:
            connection = Connection(uuid.uuid4(), server, 445)
            session = Session(connection, username, password)
            tree = TreeConnect(session, r"\\%s\ADMIN$" % server)
            paexec_file = Open(tree, "PAExec.exe")

            connection.connect()
            try:

                session.connect()
                tree.connect()

                paexec_file.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.FILE_WRITE_DATA,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   ShareAccess.FILE_SHARE_READ,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
                paexec_file.write(pkgutil.get_data('pypsexec', 'paexec.exe'),
                                  0)
                paexec_file.close(get_attributes=False)

                yield session
            finally:
                paexec_file.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.DELETE,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   ShareAccess.FILE_SHARE_DELETE,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_DELETE_ON_CLOSE)
                paexec_file.close(get_attributes=False)
                connection.disconnect(True)

        else:
            pytest.skip("PYPSEXEC_SERVER, PYPSEXEC_USERNAME, PYPSEXEC_PASSWORD"
                        " environment variables were not set. Integration "
                        "tests will be skipped")
Esempio n. 3
0
class SMBRawIO(io.RawIOBase):

    FILE_TYPE = None  # 'file', 'dir', or None (for unknown)
    _INVALID_MODE = ''

    def __init__(self,
                 path,
                 mode='r',
                 share_access=None,
                 desired_access=None,
                 file_attributes=None,
                 create_options=0,
                 buffer_size=MAX_PAYLOAD_SIZE,
                 **kwargs):
        tree, fd_path = get_smb_tree(path, **kwargs)
        self.share_access = share_access
        self.fd = Open(tree, fd_path)
        self._mode = mode
        self._name = path
        self._offset = 0
        self._flush = False
        self._buffer_size = buffer_size

        if desired_access is None:
            desired_access = 0

            # While we can open a directory, the values for FilePipePrinterAccessMask also apply to Dirs so just use
            # the same enum to simplify code.
            if 'r' in self.mode or '+' in self.mode:
                desired_access |= FilePipePrinterAccessMask.FILE_READ_DATA | \
                                  FilePipePrinterAccessMask.FILE_READ_ATTRIBUTES | \
                                  FilePipePrinterAccessMask.FILE_READ_EA
            if 'w' in self.mode or 'x' in self.mode or 'a' in self.mode or '+' in self.mode:
                desired_access |= FilePipePrinterAccessMask.FILE_WRITE_DATA | \
                                  FilePipePrinterAccessMask.FILE_WRITE_ATTRIBUTES | \
                                  FilePipePrinterAccessMask.FILE_WRITE_EA
        self._desired_access = desired_access

        if file_attributes is None:
            file_attributes = FileAttributes.FILE_ATTRIBUTE_DIRECTORY if self.FILE_TYPE == 'dir' \
                else FileAttributes.FILE_ATTRIBUTE_NORMAL
        self._file_attributes = file_attributes

        self._create_options = create_options
        self._create_options |= {
            'dir': CreateOptions.FILE_DIRECTORY_FILE,
            'file': CreateOptions.FILE_NON_DIRECTORY_FILE,
        }.get(self.FILE_TYPE, 0)

        super(SMBRawIO, self).__init__()

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @property
    def closed(self):
        return not self.fd.connected

    @property
    def mode(self):
        return self._mode

    @property
    def name(self):
        return self._name

    def close(self, transaction=None):
        if transaction:
            transaction += self.fd.close(send=False)
        else:
            self.fd.close()

    def flush(self):
        if self._flush and self.FILE_TYPE != 'pipe':
            self.fd.flush()

    def open(self, transaction=None):
        if not self.closed:
            return

        share_access = _parse_share_access(self.share_access, self.mode)
        create_disposition = _parse_mode(self.mode, invalid=self._INVALID_MODE)

        try:
            # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wpo/feeb3122-cfe0-4b34-821d-e31c036d763c
            # Impersonation on SMB has little meaning when opening files but is important if using RPC so set to a sane
            # default of Impersonation.
            open_result = self.fd.create(
                ImpersonationLevel.Impersonation,
                self._desired_access,
                self._file_attributes,
                share_access,
                create_disposition,
                self._create_options,
                send=(transaction is None),
            )
        except SMBResponseException as exc:
            raise SMBOSError(exc.status, self.name)

        if transaction is not None:
            transaction += open_result
        elif 'a' in self.mode and self.FILE_TYPE != 'pipe':
            self._offset = self.fd.end_of_file

    def readable(self):
        """ True if file was opened in a read mode. """
        return 'r' in self.mode or '+' in self.mode

    def seek(self, offset, whence=SEEK_SET):
        """
        Move to new file position and return the file position.

        Argument offset is a byte count.  Optional argument whence defaults to
        SEEK_SET or 0 (offset from start of file, offset should be >= 0); other values
        are SEEK_CUR or 1 (move relative to current position, positive or negative),
        and SEEK_END or 2 (move relative to end of file, usually negative, although
        many platforms allow seeking beyond the end of a file).

        Note that not all file objects are seekable.
        """
        seek_offset = {
            SEEK_SET: 0,
            SEEK_CUR: self._offset,
            SEEK_END: self.fd.end_of_file,
        }[whence]
        self._offset = seek_offset + offset
        return self._offset

    def seekable(self):
        """ True if file supports random-access. """
        return True

    def tell(self):
        """
        Current file position.

        Can raise OSError for non seekable files.
        """
        return self._offset

    def truncate(self, size):
        """
        Truncate the file to at most size bytes and return the truncated size.

        Size defaults to the current file position, as returned by tell().
        The current file position is changed to the value of size.
        """
        with SMBFileTransaction(self) as transaction:
            eof_info = FileEndOfFileInformation()
            eof_info['end_of_file'] = size
            set_info(transaction, eof_info)

        self.fd.end_of_file = size
        self._flush = True
        return size

    def writable(self):
        """ True if file was opened in a write mode. """
        return 'w' in self.mode or 'x' in self.mode or 'a' in self.mode or '+' in self.mode

    def readall(self):
        """
        Read and return all the bytes from the stream until EOF, using
        multiple calls to the stream if necessary.

        :return: The byte string read from the SMB file.
        """
        data = b""
        remaining_bytes = self.fd.end_of_file - self._offset
        while len(data) < remaining_bytes or self.FILE_TYPE == 'pipe':
            try:
                data += self.fd.read(self._offset, self._buffer_size)
            except SMBResponseException as exc:
                if exc.status == NtStatus.STATUS_PIPE_BROKEN:
                    break
                raise

            if self.FILE_TYPE != 'pipe':
                self._offset += len(data)

        return data

    def readinto(self, b):
        """
        Read bytes into a pre-allocated, writable bytes-like object b, and
        return the number of bytes read.

        :param b: bytes-like object to read the data into.
        :return: The number of bytes read.
        """
        if self._offset >= self.fd.end_of_file and self.FILE_TYPE != 'pipe':
            return 0

        try:
            file_bytes = self.fd.read(self._offset, len(b))
        except SMBResponseException as exc:
            if exc.status == NtStatus.STATUS_PIPE_BROKEN:
                file_bytes = b""
            else:
                raise

        b[:len(file_bytes)] = file_bytes

        if self.FILE_TYPE != 'pipe':
            self._offset += len(file_bytes)

        return len(file_bytes)

    def write(self, b):
        """
        Write buffer b to file, return number of bytes written.

        Only makes one system call, so not all of the data may be written.
        The number of bytes actually written is returned.
        """
        if isinstance(b, memoryview):
            b = b.tobytes()

        with SMBFileTransaction(self) as transaction:
            transaction += self.fd.write(b, offset=self._offset, send=False)

            # Send the request with an SMB2QueryInfoRequest for FileStandardInformation so we can update the end of
            # file stored internally.
            if self.FILE_TYPE != 'pipe':
                query_info(transaction, FileStandardInformation)

        bytes_written = transaction.results[0]
        if self.FILE_TYPE != 'pipe':
            self._offset += bytes_written
            self.fd.end_of_file = transaction.results[1][
                'end_of_file'].get_value()
            self._flush = True

        return bytes_written
Esempio n. 4
0
class SCMRApi(object):
    def __init__(self, smb_session):
        # connect to the IPC tree and open a handle at svcctl
        self.tree = TreeConnect(
            smb_session, r"\\%s\IPC$" % smb_session.connection.server_name)
        self.handle = Open(self.tree, "svcctl")
        self.call_id = 0

    def open(self):
        log.debug("Connecting to SMB Tree %s for SCMR" % self.tree.share_name)
        self.tree.connect()

        log.debug("Opening handle to svcctl pipe")
        self.handle.create(
            ImpersonationLevel.Impersonation,
            FilePipePrinterAccessMask.GENERIC_READ
            | FilePipePrinterAccessMask.GENERIC_WRITE, 0,
            ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE
            | ShareAccess.FILE_SHARE_DELETE, CreateDisposition.FILE_OPEN,
            CreateOptions.FILE_NON_DIRECTORY_FILE)

        # we need to bind svcctl to SCManagerW over DCE/RPC
        bind = BindPDU()
        bind['pfx_flags'].set_flag(PFlags.PFC_FIRST_FRAG)
        bind['pfx_flags'].set_flag(PFlags.PFC_LAST_FRAG)
        bind['packed_drep'] = DataRepresentationFormat()
        bind['call_id'] = self.call_id
        self.call_id += 1

        context_ndr = ContextElement()
        context_ndr['context_id'] = 0
        context_ndr['abstract_syntax'] = SyntaxIdElement()
        context_ndr['abstract_syntax']['uuid'] = \
            uuid.UUID("367ABB81-9844-35F1-AD32-98F038001003")
        context_ndr['abstract_syntax']['version'] = 2

        # https://msdn.microsoft.com/en-us/library/cc243843.aspx
        ndr_syntax = SyntaxIdElement()
        ndr_syntax['uuid'] = uuid.UUID("8a885d04-1ceb-11c9-9fe8-08002b104860")
        ndr_syntax['version'] = 2
        context_ndr['transfer_syntaxes'] = [ndr_syntax]

        context_bind = ContextElement()
        context_bind['context_id'] = 1
        context_bind['abstract_syntax'] = SyntaxIdElement()
        context_bind['abstract_syntax']['uuid'] = \
            uuid.UUID("367ABB81-9844-35F1-AD32-98F038001003")
        context_bind['abstract_syntax']['version'] = 2

        # https://msdn.microsoft.com/en-us/library/cc243715.aspx
        # uuid prefix = 6CB71C2C-9812-4540
        # uuid prefix bytes = b'\x2c\x1c\xb7\x6c\x12\x98\x40\x45'
        # BindTimeFeatureNegotiateBitmask
        # https://msdn.microsoft.com/en-us/library/cc243884.aspx
        # SecurityContextMultiplexingSupported = 0x01
        # KeepConnectionOnOrphanSupported = 0x02
        # version number is 1
        bind_syntax = SyntaxIdElement()
        bind_syntax['uuid'] = b'\x2c\x1c\xb7\x6c\x12\x98\x40\x45' \
                              b'\x03\x00\x00\x00\x00\x00\x00\x00'
        bind_syntax['version'] = 1
        context_bind['transfer_syntaxes'] = [bind_syntax]

        bind['context_elems'] = [context_ndr, context_bind]
        bind_data = bind.pack()

        log.info("Sending bind request to svcctl")
        log.debug(str(bind))
        self.handle.write(bind_data)

        log.info("Receiving bind result for svcctl")
        bind_data = self.handle.read(0, 1024)
        bind_result = parse_pdu(bind_data)
        log.debug(str(bind_result))
        if not isinstance(bind_result, BindAckPDU):
            raise PDUException("Expecting BindAckPDU for initial bind result "
                               "but got: %s" % str(bind_result))

    def close(self):
        log.info("Closing bind to svcctl")
        self.handle.close(False)
        self.tree.disconnect()

    # SCMR Functions below

    def close_service_handle_w(self, handle):
        # https://msdn.microsoft.com/en-us/library/cc245920.aspx
        opnum = 0

        res = self._invoke("RCloseServiceHandleW", opnum, handle)
        handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "RCloseServiceHandleW")
        return handle

    def control_service(self, service_handle, control_code):
        # https://msdn.microsoft.com/en-us/library/cc245921.aspx
        opnum = 1

        data = service_handle
        data += struct.pack("<i", control_code)

        res = self._invoke("RControlService", opnum, data)
        return_code = struct.unpack("<i", res[-4:])[0]
        self._parse_error(return_code, "RControlService")

        service_status = ServiceStatus()
        service_status.unpack(res[:-4])

        return service_status

    def delete_service(self, service_handle):
        # https://msdn.microsoft.com/en-us/library/cc245926.aspx
        opnum = 2

        res = self._invoke("RDeleteService", opnum, service_handle)
        return_code = struct.unpack("<i", res)[0]
        self._parse_error(return_code, "RDeleteService")

    def query_service_status(self, service_handle):
        # https://msdn.microsoft.com/en-us/library/cc245952.aspx
        opnum = 6

        res = self._invoke("RQueryServiceStatus", opnum, service_handle)
        return_code = struct.unpack("<i", res[-4:])[0]
        self._parse_error(return_code, "RQueryServiceStatus")

        service_status = ServiceStatus()
        service_status.unpack(res[:-4])

        return service_status

    def enum_services_status_w(self, server_handle, service_type,
                               service_state):
        """
        Enumerates the services based on the criteria selected

        :param server_handle: A handle to SCMR
        :param service_type: ServiceType flags to filter by service type
        :param service_state: EnumServiceState enum value
        :return: List dictionaries with the following entries
            service_name: The service name of the service
            display_name: The display name of the service
            service_status: ServiceStatus structure of the service
        """
        # https://msdn.microsoft.com/en-us/library/cc245933.aspx
        opnum = 14

        # sent 0 bytes on the buffer size for the 1st request to get the
        # buffer size that is required
        req_data = server_handle
        req_data += struct.pack("<i", service_type)
        req_data += struct.pack("<i", service_state)
        req_data += struct.pack("<i", 0)
        req_data += b"\x00\x00\x00\x00"
        res = self._invoke("REnumServicesStatusW", opnum, req_data)

        # now send another request with the total buffer size sent
        buffer_size = struct.unpack("<i", res[4:8])[0]
        req_data = server_handle
        req_data += struct.pack("<i", service_type)
        req_data += struct.pack("<i", service_state)
        req_data += res[4:8]
        req_data += b"\x00\x00\x00\x00"

        try:
            res = self._invoke("REnumServicesStatusW", opnum, req_data)
            data = res
        except SMBResponseException as exc:
            if exc.status != NtStatus.STATUS_BUFFER_OVERFLOW:
                raise exc

            ioctl_resp = SMB2IOCTLResponse()
            ioctl_resp.unpack(exc.header['data'].get_value())
            pdu_resp = self._parse_pdu(ioctl_resp['buffer'].get_value(), opnum)
            read_data = self.handle.read(0, 3256)  # 4280 - 1024
            data = pdu_resp + read_data

        while len(data) < buffer_size:
            read_data = self.handle.read(0, 4280)
            data += self._parse_pdu(read_data, opnum)

        return_code = struct.unpack("<i", data[-4:])[0]
        self._parse_error(return_code, "REnumServicesStatusW")

        # now we have all the data, let's unpack it
        services = []
        services_returned = struct.unpack("<i", data[-12:-8])[0]
        offset = 4
        for i in range(0, services_returned):
            name_offset = struct.unpack("<i", data[offset:4 + offset])[0]
            disp_offset = struct.unpack("<i", data[4 + offset:8 + offset])[0]
            service_status = ServiceStatus()
            service_name = data[name_offset + 4:].split(b"\x00\x00")[0]
            display_name = data[disp_offset + 4:].split(b"\x00\x00")[0]
            service_status.unpack(data[offset + 8:])

            service_info = {
                "display_name": (display_name + b"\x00").decode('utf-16-le'),
                "service_name": (service_name + b"\x00").decode('utf-16-le'),
                "service_status": service_status
            }
            services.append(service_info)
            offset += 8 + len(service_status)

        return services

    def open_sc_manager_w(self, machine_name, database_name, desired_access):
        # https://msdn.microsoft.com/en-us/library/cc245942.aspx
        opnum = 15

        data = self._marshal_string(machine_name, unique=True)
        data += self._marshal_string(database_name)
        data += struct.pack("<i", desired_access)

        res = self._invoke("ROpenSCManagerW", opnum, data)
        server_handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "ROpenSCManagerW")
        return server_handle

    def open_service_w(self, server_handle, service_name, desired_access):
        # https://msdn.microsoft.com/en-us/library/cc245944.aspx
        opnum = 16

        data = server_handle
        data += self._marshal_string(service_name)
        data += struct.pack("<i", desired_access)

        res = self._invoke("ROpenServiceW", opnum, data)
        service_handle = res[:20]
        return_code = struct.unpack("<i", res[20:])[0]
        self._parse_error(return_code, "ROpenServiceW")
        return service_handle

    def start_service_w(self, service_handle, *args):
        opnum = 19

        data = service_handle
        data += struct.pack("<i", len(args))
        data += b"".join([self._marshal_string(arg) for arg in args])
        data += b"\x00" * 4  # terminate arg list

        res = self._invoke("RStartServiceW", opnum, data)
        return_code = struct.unpack("<i", res)[0]
        self._parse_error(return_code, "RStartServiceW")

    def create_service_w(self, server_handle, service_name, display_name,
                         desired_access, service_type, start_type,
                         error_control, path, load_order_group, tag_id,
                         dependencies, username, password):
        # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e
        opnum = 12

        data = server_handle
        data += self._marshal_string(service_name)
        data += self._marshal_string(display_name, unique=True)
        data += struct.pack("<i", desired_access)
        data += struct.pack("<i", service_type)
        data += struct.pack("<i", start_type)
        data += struct.pack("<i", error_control)
        data += self._marshal_string(path)
        data += self._marshal_string(load_order_group)
        data += struct.pack("<i", tag_id)

        # TODO: convert list of string to a byte object
        dependencies_bytes = dependencies if dependencies else b"\x00" * 6
        data += dependencies_bytes
        dependencies_length = len(dependencies) if dependencies else 0
        data += struct.pack("<i", dependencies_length)

        data += self._marshal_string(username)

        pass_bytes = self._marshal_string(password)
        data += pass_bytes
        pass_len = len(pass_bytes) if password else 0
        data += struct.pack("<i", pass_len)

        res = self._invoke("RCreateServiceW", opnum, data)
        tag_id = res[0:4]
        service_handle = res[4:24]
        return_code = struct.unpack("<i", res[24:])[0]
        self._parse_error(return_code, "RCreateServiceW")
        return tag_id, service_handle

    def _invoke(self, function_name, opnum, data):
        req = RequestPDU()
        req['pfx_flags'].set_flag(PFlags.PFC_FIRST_FRAG)
        req['pfx_flags'].set_flag(PFlags.PFC_LAST_FRAG)
        req['packed_drep'] = DataRepresentationFormat()
        req['call_id'] = self.call_id
        self.call_id += 1

        req['opnum'] = opnum
        req['stub_data'] = data

        ioctl_request = SMB2IOCTLRequest()
        ioctl_request['ctl_code'] = CtlCode.FSCTL_PIPE_TRANSCEIVE
        ioctl_request['file_id'] = self.handle.file_id
        ioctl_request['max_output_response'] = 1024
        ioctl_request['flags'] = IOCTLFlags.SMB2_0_IOCTL_IS_FSCTL
        ioctl_request['buffer'] = req

        session_id = self.tree.session.session_id
        tree_id = self.tree.tree_connect_id
        log.info("Sending svcctl RPC request for %s" % function_name)
        log.debug(str(req))
        request = self.tree.session.connection.send(ioctl_request,
                                                    sid=session_id,
                                                    tid=tree_id)
        log.info("Receiving svcctl RPC response for %s" % function_name)
        resp = self.tree.session.connection.receive(request)
        ioctl_resp = SMB2IOCTLResponse()
        ioctl_resp.unpack(resp['data'].get_value())
        log.debug(str(ioctl_resp))

        pdu_resp = self._parse_pdu(ioctl_resp['buffer'].get_value(), opnum)
        return pdu_resp

    def _parse_pdu(self, data, opnum):
        pdu_resp = parse_pdu(data)
        if not isinstance(pdu_resp, ResponsePDU):
            raise PDUException("Expecting ResponsePDU for opnum %d response "
                               "but got: %s" % (opnum, str(pdu_resp)))
        return pdu_resp['stub_data'].get_value()

    def _parse_error(self, return_code, function_name):
        error_string = "ERROR_UNKNOWN"
        for error_name, error_val in vars(ScmrReturnValues).items():
            if isinstance(error_val, int) and error_val == return_code:
                error_string = error_name
                break
        if not error_string.startswith("ERROR_SUCCESS"):
            raise SCMRException(function_name, return_code, error_string)

    def _marshal_string(self, string, unique=False, max_count=None):
        """
        Strings are encoding as a UTF-16-LE byte structure and are marshalled
        in a particular format to send over RPC. The format is as follows

            Referent ID (Int32): A unique ID for the string, we just set to 1
            Max Count (Int32): If the server can return a value, this is the
                size that can be returned in the buffer otherwise just the
                numbers of chars in the input string
            Offset (Int32): The offset of the string, defaults to 0
            Actual Count (Int32): The number of chars (not bytes) or the string
                itself including the NULL terminator
            Bytes (Bytes): The string encoded as a UTF-16-LE byte string with
                a NULL terminator
            Padding (Bytes): The value must align to a 4-byte boundary so this
                is some NULL bytes to pad the length

        :param string: The string to marshal
        :param unique: Whether the string is unique and requires an ID
        :return: A byte string of the marshaled string
        """
        # return NULL Pointer for a null string
        if not string:
            return b"\x00" * 4

        referent = b"\x00\x00\x00\x01" if unique else b""
        unicode_string = string.encode("utf-16-le") + b"\x00\x00"
        unicode_count = int(len(unicode_string) / 2)
        count = struct.pack("<i", unicode_count)
        offset = b"\x00" * 4
        bytes = referent + count + offset + count + unicode_string

        # each parameter needs to be aligned at a 4-byte boundary so get the
        # padding length if necessary
        mod = len(bytes) % 4
        padding_len = 0 if mod == 0 else 4 - mod
        return bytes + (b"\x00" * padding_len)
Esempio n. 5
0
        CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_DIRECTORY_FILE)

    # create some files in dir and query the contents as part of a compound
    # request
    directory_file = Open(tree, r"%s\file.txt" % dir_name)
    directory_file.create(
        ImpersonationLevel.Impersonation,
        FilePipePrinterAccessMask.GENERIC_WRITE
        | FilePipePrinterAccessMask.DELETE,
        FileAttributes.FILE_ATTRIBUTE_NORMAL, ShareAccess.FILE_SHARE_READ,
        CreateDisposition.FILE_OVERWRITE_IF,
        CreateOptions.FILE_NON_DIRECTORY_FILE
        | CreateOptions.FILE_DELETE_ON_CLOSE)

    compound_messages = [
        directory_file.write("Hello World".encode('utf-8'), 0, send=False),
        dir_open.query_directory("*",
                                 FileInformationClass.FILE_NAMES_INFORMATION,
                                 send=False),
        directory_file.close(False, send=False),
        dir_open.close(False, send=False)
    ]
    requests = connection.send_compound([x[0] for x in compound_messages],
                                        session.session_id,
                                        tree.tree_connect_id)
    responses = []
    for i, request in enumerate(requests):
        response = compound_messages[i][1](request)
        responses.append(response)

    dir_files = []
Esempio n. 6
0
class SMBRawIO(io.RawIOBase):

    FILE_TYPE = None  # 'file', 'dir', or None (for unknown)
    _INVALID_MODE = ''

    def __init__(self, path, mode='r', share_access=None, desired_access=None, file_attributes=None,
                 create_options=0, **kwargs):
        tree, fd_path = get_smb_tree(path, **kwargs)
        self.share_access = share_access
        self.fd = Open(tree, fd_path)
        self._mode = mode
        self._name = path
        self._offset = 0
        self._flush = False
        self.__kwargs = kwargs  # Used in open for DFS referrals

        if desired_access is None:
            desired_access = 0

            # While we can open a directory, the values for FilePipePrinterAccessMask also apply to Dirs so just use
            # the same enum to simplify code.
            if 'r' in self.mode or '+' in self.mode:
                desired_access |= FilePipePrinterAccessMask.FILE_READ_DATA | \
                                  FilePipePrinterAccessMask.FILE_READ_ATTRIBUTES | \
                                  FilePipePrinterAccessMask.FILE_READ_EA
            if 'w' in self.mode or 'x' in self.mode or 'a' in self.mode or '+' in self.mode:
                desired_access |= FilePipePrinterAccessMask.FILE_WRITE_DATA | \
                                  FilePipePrinterAccessMask.FILE_WRITE_ATTRIBUTES | \
                                  FilePipePrinterAccessMask.FILE_WRITE_EA
        self._desired_access = desired_access

        if file_attributes is None:
            file_attributes = FileAttributes.FILE_ATTRIBUTE_DIRECTORY if self.FILE_TYPE == 'dir' \
                else FileAttributes.FILE_ATTRIBUTE_NORMAL
        self._file_attributes = file_attributes

        self._create_options = create_options
        self._create_options |= {
            'dir': CreateOptions.FILE_DIRECTORY_FILE,
            'file': CreateOptions.FILE_NON_DIRECTORY_FILE,
        }.get(self.FILE_TYPE, 0)

        super(SMBRawIO, self).__init__()

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @property
    def closed(self):
        return not self.fd.connected

    @property
    def mode(self):
        return self._mode

    @property
    def name(self):
        return self._name

    def close(self, transaction=None):
        if transaction:
            transaction += self.fd.close(send=False)
        else:
            self.fd.close()

    def flush(self):
        if self._flush and self.FILE_TYPE != 'pipe':
            self.fd.flush()

    def open(self, transaction=None):
        if not self.closed:
            return

        share_access = _parse_share_access(self.share_access, self.mode)
        create_disposition = _parse_mode(self.mode, invalid=self._INVALID_MODE)

        try:
            # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wpo/feeb3122-cfe0-4b34-821d-e31c036d763c
            # Impersonation on SMB has little meaning when opening files but is important if using RPC so set to a sane
            # default of Impersonation.
            open_result = self.fd.create(
                ImpersonationLevel.Impersonation,
                self._desired_access,
                self._file_attributes,
                share_access,
                create_disposition,
                self._create_options,
                send=(transaction is None),
            )
        except (PathNotCovered, ObjectNameNotFound, ObjectPathNotFound) as exc:
            # The MS-DFSC docs status that STATUS_PATH_NOT_COVERED is used when encountering a link to a different
            # server but Samba seems to return the generic name or path not found.
            if not self.fd.tree_connect.is_dfs_share:
                raise SMBOSError(exc.status, self.name)

            # Path is on a DFS root that is linked to another server.
            client_config = ClientConfig()
            referral = dfs_request(self.fd.tree_connect, self.name[1:])
            client_config.cache_referral(referral)
            info = client_config.lookup_referral([p for p in self.name.split("\\") if p])

            for target in info:
                new_path = self.name.replace(info.dfs_path, target.target_path, 1)

                try:
                    tree, fd_path = get_smb_tree(new_path, **self.__kwargs)
                    self.fd = Open(tree, fd_path)
                    self.open(transaction=transaction)

                except SMBResponseException as link_exc:
                    log.warning("Failed to connect to DFS link target %s: %s" % (str(target), link_exc))

                else:
                    # Record the target that worked for future reference.
                    info.target_hint = target
                    break

            else:
                # None of the targets worked so raise the original error.
                raise SMBOSError(exc.status, self.name)

            return

        except SMBResponseException as exc:
            raise SMBOSError(exc.status, self.name)

        if transaction is not None:
            transaction += open_result
        elif 'a' in self.mode and self.FILE_TYPE != 'pipe':
            self._offset = self.fd.end_of_file

    def readable(self):
        """ True if file was opened in a read mode. """
        return 'r' in self.mode or '+' in self.mode

    def seek(self, offset, whence=SEEK_SET):
        """
        Move to new file position and return the file position.

        Argument offset is a byte count.  Optional argument whence defaults to
        SEEK_SET or 0 (offset from start of file, offset should be >= 0); other values
        are SEEK_CUR or 1 (move relative to current position, positive or negative),
        and SEEK_END or 2 (move relative to end of file, usually negative, although
        many platforms allow seeking beyond the end of a file).

        Note that not all file objects are seekable.
        """
        seek_offset = {
            SEEK_SET: 0,
            SEEK_CUR: self._offset,
            SEEK_END: self.fd.end_of_file,
        }[whence]
        self._offset = seek_offset + offset
        return self._offset

    def seekable(self):
        """ True if file supports random-access. """
        return True

    def tell(self):
        """
        Current file position.

        Can raise OSError for non seekable files.
        """
        return self._offset

    def truncate(self, size):
        """
        Truncate the file to at most size bytes and return the truncated size.

        Size defaults to the current file position, as returned by tell().
        The current file position is changed to the value of size.
        """
        with SMBFileTransaction(self) as transaction:
            eof_info = FileEndOfFileInformation()
            eof_info['end_of_file'] = size
            set_info(transaction, eof_info)

        self.fd.end_of_file = size
        self._flush = True
        return size

    def writable(self):
        """ True if file was opened in a write mode. """
        return 'w' in self.mode or 'x' in self.mode or 'a' in self.mode or '+' in self.mode

    def readall(self):
        """
        Read and return all the bytes from the stream until EOF, using
        multiple calls to the stream if necessary.

        :return: The byte string read from the SMB file.
        """
        data = bytearray()
        while True:
            read_length = min(
                # We always want to be reading a minimum of 64KiB.
                max(self.fd.end_of_file - self._offset, MAX_PAYLOAD_SIZE),
                self.fd.connection.max_read_size  # We can never read more than this.
            )

            buffer = bytearray(b'\x00' * read_length)
            bytes_read = self.readinto(buffer)
            if not bytes_read:
                break

            data += buffer[:bytes_read]

        return bytes(data)

    def readinto(self, b):
        """
        Read bytes into a pre-allocated, writable bytes-like object b, and
        return the number of bytes read. This may read less bytes than
        requested as it depends on the negotiated read size and SMB credits
        available.

        :param b: bytes-like object to read the data into.
        :return: The number of bytes read.
        """
        if self._offset >= self.fd.end_of_file and self.FILE_TYPE != 'pipe':
            return 0

        chunk_size, credit_request = _chunk_size(self.fd.connection, len(b), 'read')

        read_msg, recv_func = self.fd.read(self._offset, chunk_size, send=False)
        request = self.fd.connection.send(
            read_msg,
            sid=self.fd.tree_connect.session.session_id,
            tid=self.fd.tree_connect.tree_connect_id,
            credit_request=credit_request
        )

        try:
            file_bytes = recv_func(request)
        except PipeBroken:
            # A pipe will block until it returns the data available or was closed/broken.
            file_bytes = b""

        b[:len(file_bytes)] = file_bytes

        if self.FILE_TYPE != 'pipe':
            self._offset += len(file_bytes)

        return len(file_bytes)

    def write(self, b):
        """
        Write buffer b to file, return number of bytes written.

        Only makes one system call, so not all of the data may be written.
        The number of bytes actually written is returned. This can be less than
        the length of b as it depends on the underlying connection.
        """
        chunk_size, credit_request = _chunk_size(self.fd.connection, len(b), 'write')

        # Python 2 compat, can be removed and just use the else statement.
        if isinstance(b, memoryview):
            data = b[:chunk_size].tobytes()
        else:
            data = bytes(b[:chunk_size])

        write_msg, recv_func = self.fd.write(data, offset=self._offset, send=False)
        request = self.fd.connection.send(
            write_msg,
            sid=self.fd.tree_connect.session.session_id,
            tid=self.fd.tree_connect.tree_connect_id,
            credit_request=credit_request
        )
        bytes_written = recv_func(request)

        if self.FILE_TYPE != 'pipe':
            self._offset += bytes_written
            self.fd.end_of_file = max(self.fd.end_of_file, self._offset)
            self._flush = True

        return bytes_written
Esempio n. 7
0
        ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE,
        CreateDisposition.FILE_OVERWRITE_IF,
        CreateOptions.FILE_NON_DIRECTORY_FILE, create_contexts)

    # as the raw structure 'maximal_access' is an IntField, we create our own
    # flag field, set the value and get the human readble string
    max_access = FlagField(size=4,
                           flag_type=FilePipePrinterAccessMask,
                           flag_strict=False)
    max_access.set_value(open_info[0]['maximal_access'].get_value())
    print("Maximum access mask for file %s\\%s: %s" %
          (share, file_name, str(max_access)))

    # write to a file
    text = "Hello World, what a nice day to play with SMB"
    file_open.write(text.encode('utf-8'), 0)

    # read from a file
    file_text = file_open.read(0, 1024)
    print("Text of file %s\\%s: %s" %
          (share, file_name, file_text.decode('utf-8')))
    file_open.close(False)

    # read and delete a file in a single SMB packet instead of 3
    file_open = Open(tree, file_name)
    delete_msgs = [
        file_open.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.GENERIC_READ
                         | FilePipePrinterAccessMask.DELETE,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         0,