Exemplo n.º 1
0
    def _find_volumes(self, volume_system, vstype='detect'):
        """Finds all volumes based on the pytsk3 library."""

        try:
            # noinspection PyUnresolvedReferences
            import pytsk3
        except ImportError:
            logger.error("pytsk3 not installed, could not detect volumes")
            raise ModuleNotFoundError("pytsk3")

        baseimage = None
        try:
            # ewf raw image is now available on base mountpoint
            # either as ewf1 file or as .dd file
            raw_path = volume_system.parent.get_raw_path()
            # noinspection PyBroadException
            try:
                baseimage = pytsk3.Img_Info(raw_path)
            except Exception:
                logger.error(
                    "Failed retrieving image info (possible empty image).",
                    exc_info=True)
                return []

            try:
                volumes = pytsk3.Volume_Info(
                    baseimage, getattr(pytsk3,
                                       'TSK_VS_TYPE_' + vstype.upper()),
                    volume_system.parent.offset //
                    volume_system.disk.block_size)
                volume_system.volume_source = 'multi'
                return volumes
            except Exception as e:
                # some bug in sleuthkit makes detection sometimes difficult, so we hack around it:
                if "(GPT or DOS at 0)" in str(e) and vstype != 'gpt':
                    volume_system.vstype = 'gpt'
                    # noinspection PyBroadException
                    try:
                        logger.warning(
                            "Error in retrieving volume info: TSK couldn't decide between GPT and DOS, "
                            "choosing GPT for you. Use --vstype=dos to force DOS.",
                            exc_info=True)
                        volumes = pytsk3.Volume_Info(
                            baseimage, getattr(pytsk3, 'TSK_VS_TYPE_GPT'))
                        volume_system.volume_source = 'multi'
                        return volumes
                    except Exception as e:
                        logger.exception(
                            "Failed retrieving image info (possible empty image)."
                        )
                        raise SubsystemError(e)
                else:
                    logger.exception(
                        "Failed retrieving image info (possible empty image).")
                    raise SubsystemError(e)
        finally:
            if baseimage:
                baseimage.close()
                del baseimage
Exemplo n.º 2
0
    def carve(self, freespace=True):
        """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its
        own interface that temporarily takes over the shell.

        :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True)
        :type freespace: bool
        :return: string to the path where carved data is available
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubsystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        :raises NoLoopbackAvailableError: if there is no loopback available (only when volume has no slot number)
        """

        self._make_mountpoint(var_name='carve', suffix="carve", in_paths=True)

        # if no slot, we need to make a loopback that we can use to carve the volume
        loopback_was_created_for_carving = False
        if not self.slot:
            if not self.loopback:
                self._find_loopback()
                # Can't carve if volume has no slot number and can't be mounted on loopback.
                loopback_was_created_for_carving = True

            # noinspection PyBroadException
            try:
                _util.check_call_([
                    "photorec", "/d",
                    self._paths['carve'] + os.sep, "/cmd", self.loopback,
                    ("freespace," if freespace else "") + "search"
                ])

                # clean out the loop device if we created it specifically for carving
                if loopback_was_created_for_carving:
                    # noinspection PyBroadException
                    try:
                        _util.check_call_(['losetup', '-d', self.loopback])
                    except Exception:
                        pass
                    else:
                        self.loopback = ""

                return self._paths['carve']
            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
        else:
            # noinspection PyBroadException
            try:
                _util.check_call_([
                    "photorec", "/d", self._paths['carve'] + os.sep, "/cmd",
                    self.get_raw_path(),
                    str(self.slot) + (",freespace" if freespace else "") +
                    ",search"
                ])
                return self._paths['carve']

            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
Exemplo n.º 3
0
    def _detect_vss_volumes(self, path):
        """Detect volume shadow copy volumes in the specified path."""

        try:
            volume_info = _util.check_output_([
                "vshadowinfo", "-o",
                str(self.parent.offset),
                self.parent.get_raw_path()
            ])
        except Exception as e:
            logger.exception(
                "Failed obtaining info from the volume shadow copies.")
            raise SubsystemError(e)

        current_store = None
        for line in volume_info.splitlines():
            line = line.strip()
            if line.startswith("Store:"):
                idx = line.split(":")[-1].strip()
                current_store = self._make_subvolume(
                    index=self._format_index(idx), flag='alloc', offset=0)
                current_store._paths['vss_store'] = os.path.join(
                    path, 'vss' + idx)
                current_store.info['fsdescription'] = 'VSS Store'
            elif line.startswith("Volume size"):
                current_store.size = int(
                    line.split(":")[-1].strip().split()[0])
            elif line.startswith("Creation time"):
                current_store.info['creation_time'] = line.split(
                    ":")[-1].strip()

        return self.volumes
Exemplo n.º 4
0
    def mount(self):
        """Based on the file system type as determined by :func:`determine_fs_type`, the proper mount command is executed
        for this volume. The volume is mounted in a temporary path (or a pretty path if :attr:`pretty` is enabled) in
        the mountpoint as specified by :attr:`mountpoint`.

        If the file system type is a LUKS container or LVM, additional methods may be called, adding subvolumes to
        :attr:`volumes`

        :raises NotMountedError: if the parent volume/disk is not mounted
        :raises NoMountpointAvailableError: if no mountpoint was found
        :raises NoLoopbackAvailableError: if no loopback device was found
        :raises UnsupportedFilesystemError: if the fstype is not supported for mounting
        :raises SubsystemError: if one of the underlying commands failed
        """

        if not self.parent.is_mounted:
            raise NotMountedError(self.parent)

        self.filesystem = self.determine_fs_type()
        self._load_fsstat_data()

        # Prepare mount command
        try:
            self.filesystem.mount()

            self.was_mounted = True
            self.is_mounted = True

        except Exception as e:
            logger.exception("Execution failed due to {} {}".format(type(e), e), exc_info=True)
            if not isinstance(e, ImageMounterError):
                raise SubsystemError(e)
            else:
                raise
Exemplo n.º 5
0
def clean_unmount(cmd, mountpoint, tries=5, rmdir=True):
    cmd.append(mountpoint)

    # AVFS mounts are not actually unmountable, but are symlinked.
    if os.path.exists(os.path.join(mountpoint, 'avfs')):
        os.remove(os.path.join(mountpoint, 'avfs'))
        # noinspection PyProtectedMember
        logger.debug("Removed {}".format(os.path.join(mountpoint, 'avfs')))
    elif os.path.islink(mountpoint):
        pass  # if it is a symlink, we can simply skip to removing it
    else:
        # Perform unmount
        # noinspection PyBroadException
        try:
            check_call_(cmd)
        except Exception as e:
            raise SubsystemError(e)

    # Remove mountpoint only if needed
    if not rmdir:
        return

    for _ in range(tries):
        if not os.path.ismount(mountpoint):
            # Unmount was successful, remove mountpoint
            if os.path.islink(mountpoint):
                os.unlink(mountpoint)
            else:
                os.rmdir(mountpoint)
            break
        else:
            time.sleep(1)

    if os.path.isdir(mountpoint):
        raise CleanupError()
Exemplo n.º 6
0
def check_call_(cmd, wrap_error=False, *args, **kwargs):
    logger.debug('$ {0}'.format(' '.join(cmd)))
    try:
        return subprocess.check_call(cmd, *args, **kwargs)
    except Exception as e:
        if wrap_error:
            raise SubsystemError(e)
        else:
            raise
Exemplo n.º 7
0
    def mount(self, volume):
        """Add the volume to a RAID system. The RAID array is activated as soon as the array can be activated.

        :raises NoLoopbackAvailableError: if no loopback device was found
        """

        volume._find_loopback()

        raid_status = None
        try:
            # use mdadm to mount the loopback to a md device
            # incremental and run as soon as available
            output = _util.check_output_(['mdadm', '-IR', volume.loopback],
                                         stderr=subprocess.STDOUT)

            match = re.findall(r"attached to ([^ ,]+)", output)
            if match:
                volume._paths['md'] = os.path.realpath(match[0])
                if 'which is already active' in output:
                    logger.info(
                        "RAID is already active in other volume, using %s",
                        volume._paths['md'])
                    raid_status = 'active'
                elif 'not enough to start' in output:
                    volume._paths['md'] = volume._paths['md'].replace(
                        "/dev/md/", "/dev/md")
                    logger.info(
                        "RAID volume added, but not enough to start %s",
                        volume._paths['md'])
                    raid_status = 'waiting'
                else:
                    logger.info("RAID started at {0}".format(
                        volume._paths['md']))
                    raid_status = 'active'
        except Exception as e:
            logger.exception("Failed mounting RAID.")
            volume._free_loopback()
            raise SubsystemError(e)

        # search for the RAID volume
        for v in volume.disk.parser.get_volumes():
            if v._paths.get("md") == volume._paths['md'] and v.volumes:
                logger.debug("Adding existing volume %s to volume %s",
                             v.volumes[0], volume)
                v.volumes[0].info['raid_status'] = raid_status
                volume.volumes.volumes.append(v.volumes[0])
                return v.volumes[0]
        else:
            logger.debug("Creating RAID volume for %s", self)
            container = volume.volumes._make_single_subvolume(flag='alloc',
                                                              offset=0,
                                                              size=volume.size)
            container.info['fsdescription'] = 'RAID Volume'
            container.info['raid_status'] = raid_status
            return container
Exemplo n.º 8
0
    def mount(self, volume):
        """Mounts a BDE container. Uses key material provided by the :attr:`keys` attribute. The key material should be
        provided in the same format as to :cmd:`bdemount`, used as follows:

        k:full volume encryption and tweak key
        p:passphrase
        r:recovery password
        s:file to startup key (.bek)

        :return: the Volume contained in the BDE container
        :raises ArgumentError: if the keys argument is invalid
        :raises SubsystemError: when the underlying command fails
        """

        volume._paths['bde'] = tempfile.mkdtemp(prefix='image_mounter_bde_')

        try:
            if volume.key:
                t, v = volume.key.split(':', 1)
                key = ['-' + t, v]
            else:
                logger.warning("No key material provided for %s", volume)
                key = []
        except ValueError:
            logger.exception(
                "Invalid key material provided (%s) for %s. Expecting [arg]:[value]",
                volume.key, volume)
            raise ArgumentError()

        # noinspection PyBroadException
        try:
            cmd = [
                "bdemount",
                volume.get_raw_path(), volume._paths['bde'], '-o',
                str(volume.offset)
            ]
            cmd.extend(key)
            _util.check_call_(cmd)
        except Exception as e:
            del volume._paths['bde']
            logger.exception("Failed mounting BDE volume %s.", volume)
            raise SubsystemError(e)

        container = volume.volumes._make_single_subvolume(flag='alloc',
                                                          offset=0,
                                                          size=volume.size)
        container.info['fsdescription'] = 'BDE Volume'

        return container
Exemplo n.º 9
0
    def detect_volume_shadow_copies(self):
        """Method to call vshadowmount and mount NTFS volume shadow copies.

        :return: iterable with the :class:`Volume` objects of the VSS
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubSystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        """

        self._paths['vss'] = self._make_mountpoint(suffix="vss")

        try:
            _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']])
        except Exception as e:
            logger.exception("Failed mounting the volume shadow copies.")
            raise SubsystemError(e)
        else:
            return self.volumes.detect_volumes(vstype='vss')
Exemplo n.º 10
0
    def bindmount(self, mountpoint):
        """Bind mounts the volume to another mountpoint. Only works if the volume is already mounted.

        :raises NotMountedError: when the volume is not yet mounted
        :raises SubsystemError: when the underlying command failed
        """

        if not self.mountpoint:
            raise NotMountedError(self)
        try:
            _util.check_call_(['mount', '--bind', self.mountpoint, mountpoint], stdout=subprocess.PIPE)
            if 'bindmounts' in self._paths:
                self._paths['bindmounts'].append(mountpoint)
            else:
                self._paths['bindmounts'] = [mountpoint]
            return True
        except Exception as e:
            logger.exception("Error bind mounting {0}.".format(self))
            raise SubsystemError(e)
Exemplo n.º 11
0
    def unmount(self, allow_lazy=False):
        if self.mdpath is not None:
            # MD arrays are a bit complicated, we also check all other volumes that are part of this array and
            # unmount them as well.
            logger.debug(
                "All other volumes that use %s as well will also be unmounted",
                self.mdpath)

            for v in self._iter_same_md_volumes():
                v.unmount(allow_lazy=allow_lazy)

            try:
                _util.check_output_(["mdadm", '--stop', self.mdpath],
                                    stderr=subprocess.STDOUT)
            except Exception as e:
                raise SubsystemError(e)

            self.mdpath = None

        super().unmount(allow_lazy=allow_lazy)
Exemplo n.º 12
0
    def unmount(self, allow_lazy=False):
        """Unounts the volume from the filesystem.

        :raises SubsystemError: if one of the underlying processes fails
        :raises CleanupError: if the cleanup fails
        """

        for volume in self.volumes:
            try:
                volume.unmount(allow_lazy=allow_lazy)
            except ImageMounterError:
                pass

        if self.is_mounted:
            logger.info("Unmounting volume %s", self)

        if self._paths.get('vss'):
            try:
                _util.clean_unmount(['fusermount', '-u'], self._paths['vss'])
            except SubsystemError:
                if not allow_lazy:
                    raise
                _util.clean_unmount(['fusermount', '-uz'], self._paths['vss'])
            del self._paths['vss']

        if self._paths.get('bindmounts'):
            for mp in self._paths['bindmounts']:
                _util.clean_unmount(['umount'], mp, rmdir=False)
            del self._paths['bindmounts']

        if self._paths.get('carve'):
            try:
                shutil.rmtree(self._paths['carve'])
            except OSError as e:
                raise SubsystemError(e)
            else:
                del self._paths['carve']

        self.filesystem.unmount(allow_lazy=allow_lazy)

        self.is_mounted = False
Exemplo n.º 13
0
    def unmount(self, allow_lazy=False):
        """Unounts the volume from the filesystem.

        :raises SubsystemError: if one of the underlying processes fails
        :raises CleanupError: if the cleanup fails
        """

        for volume in self.volumes:
            try:
                volume.unmount(allow_lazy=allow_lazy)
            except ImageMounterError:
                pass

        if self.is_mounted:
            logger.info("Unmounting volume %s", self)

        if self.loopback and self.info.get('volume_group'):
            _util.check_call_(
                ["lvm", 'vgchange', '-a', 'n', self.info['volume_group']],
                wrap_error=True,
                stdout=subprocess.PIPE)
            self.info['volume_group'] = ""

        if self.loopback and self._paths.get('luks'):
            _util.check_call_(['cryptsetup', 'luksClose', self._paths['luks']],
                              wrap_error=True,
                              stdout=subprocess.PIPE)
            del self._paths['luks']

        if self._paths.get('bde'):
            try:
                _util.clean_unmount(['fusermount', '-u'], self._paths['bde'])
            except SubsystemError:
                if not allow_lazy:
                    raise
                _util.clean_unmount(['fusermount', '-uz'], self._paths['bde'])
            del self._paths['bde']

        if self._paths.get('md'):
            md_path = self._paths['md']
            del self._paths[
                'md']  # removing it here to ensure we do not enter an infinite loop, will add it back later

            # MD arrays are a bit complicated, we also check all other volumes that are part of this array and
            # unmount them as well.
            logger.debug(
                "All other volumes that use %s as well will also be unmounted",
                md_path)
            for v in self.disk.get_volumes():
                if v != self and v._paths.get('md') == md_path:
                    v.unmount(allow_lazy=allow_lazy)

            try:
                _util.check_output_(["mdadm", '--stop', md_path],
                                    stderr=subprocess.STDOUT)
            except Exception as e:
                self._paths['md'] = md_path
                raise SubsystemError(e)

        if self._paths.get('vss'):
            try:
                _util.clean_unmount(['fusermount', '-u'], self._paths['vss'])
            except SubsystemError:
                if not allow_lazy:
                    raise
                _util.clean_unmount(['fusermount', '-uz'], self._paths['vss'])
            del self._paths['vss']

        if self.loopback:
            _util.check_call_(['losetup', '-d', self.loopback],
                              wrap_error=True)
            self.loopback = ""

        if self._paths.get('bindmounts'):
            for mp in self._paths['bindmounts']:
                _util.clean_unmount(['umount'], mp, rmdir=False)
            del self._paths['bindmounts']

        if self.mountpoint:
            _util.clean_unmount(['umount'], self.mountpoint)
            self.mountpoint = ""

        if self._paths.get('carve'):
            try:
                shutil.rmtree(self._paths['carve'])
            except OSError as e:
                raise SubsystemError(e)
            else:
                del self._paths['carve']

        self.is_mounted = False
Exemplo n.º 14
0
    def mount(self, fstype=None):
        """Based on the file system type as determined by :func:`determine_fs_type`, the proper mount command is executed
        for this volume. The volume is mounted in a temporary path (or a pretty path if :attr:`pretty` is enabled) in
        the mountpoint as specified by :attr:`mountpoint`.

        If the file system type is a LUKS container or LVM, additional methods may be called, adding subvolumes to
        :attr:`volumes`

        :raises NotMountedError: if the parent volume/disk is not mounted
        :raises NoMountpointAvailableError: if no mountpoint was found
        :raises NoLoopbackAvailableError: if no loopback device was found
        :raises UnsupportedFilesystemError: if the fstype is not supported for mounting
        :raises SubsystemError: if one of the underlying commands failed
        """

        if not self.parent.is_mounted:
            raise NotMountedError(self.parent)

        raw_path = self.get_raw_path()
        if fstype is None:
            fstype = self.determine_fs_type()
        self._load_fsstat_data()

        # we need a mountpoint if it is not a lvm or luks volume
        if fstype not in ('luks', 'lvm', 'bde', 'raid', 'volumesystem') and \
                fstype in FILE_SYSTEM_TYPES:
            self._make_mountpoint()

        # Prepare mount command
        try:
            def call_mount(type, opts):
                cmd = ['mount', raw_path, self.mountpoint, '-t', type, '-o', opts]
                if not self.disk.read_write:
                    cmd[-1] += ',ro'

                _util.check_output_(cmd, stderr=subprocess.STDOUT)

            if fstype == 'ext':
                call_mount('ext4', 'noexec,noload,loop,offset=' + str(self.offset))

            elif fstype == 'ufs':
                call_mount('ufs', 'ufstype=ufs2,loop,offset=' + str(self.offset))

            elif fstype == 'ntfs':
                call_mount('ntfs', 'show_sys_files,noexec,force,loop,offset=' + str(self.offset))

            elif fstype == 'xfs':
                call_mount('xfs', 'norecovery,loop,offset=' + str(self.offset))

            elif fstype == 'hfs+':
                call_mount('hfsplus', 'force,loop,offset=' + str(self.offset) + ',sizelimit=' + str(self.size))

            elif fstype in ('iso', 'udf', 'squashfs', 'cramfs', 'minix', 'fat', 'hfs'):
                mnt_type = {'iso': 'iso9660', 'fat': 'vfat'}.get(fstype, fstype)
                call_mount(mnt_type, 'loop,offset=' + str(self.offset))

            elif fstype == 'vmfs':
                self._find_loopback()
                _util.check_call_(['vmfs-fuse', self.loopback, self.mountpoint], stdout=subprocess.PIPE)

            elif fstype == 'unknown':  # mounts without specifying the filesystem type
                cmd = ['mount', raw_path, self.mountpoint, '-o', 'loop,offset=' + str(self.offset)]
                if not self.disk.read_write:
                    cmd[-1] += ',ro'

                _util.check_call_(cmd, stdout=subprocess.PIPE)

            elif fstype == 'jffs2':
                self._open_jffs2()

            elif fstype == 'luks':
                self._open_luks_container()

            elif fstype == 'bde':
                self._open_bde_container()

            elif fstype == 'lvm':
                self._open_lvm()
                self.volumes.vstype = 'lvm'
                for _ in self.volumes.detect_volumes('lvm'):
                    pass

            elif fstype == 'raid':
                self._open_raid_volume()

            elif fstype == 'dir':
                os.rmdir(self.mountpoint)
                os.symlink(raw_path, self.mountpoint)

            elif fstype == 'volumesystem':
                for _ in self.volumes.detect_volumes():
                    pass

            else:
                try:
                    size = self.size // self.disk.block_size
                except TypeError:
                    size = self.size

                logger.warning("Unsupported filesystem {0} (type: {1}, block offset: {2}, length: {3})"
                               .format(self, fstype, self.offset // self.disk.block_size, size))
                raise UnsupportedFilesystemError(fstype)

            self.was_mounted = True
            self.is_mounted = True
            self.fstype = fstype
        except Exception as e:
            logger.exception("Execution failed due to {} {}".format(type(e), e), exc_info=True)
            try:
                if self.mountpoint:
                    os.rmdir(self.mountpoint)
                    self.mountpoint = ""
                if self.loopback:
                    self.loopback = ""
            except Exception as e2:
                logger.exception("Clean-up failed", exc_info=True)

            if not isinstance(e, ImageMounterError):
                raise SubsystemError(e)
            else:
                raise
Exemplo n.º 15
0
    def _detect_mmls_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on mmls."""

        try:
            cmd = ['mmls']
            if self.parent.offset:
                cmd.extend(
                    ['-o',
                     str(self.parent.offset // self.disk.block_size)])
            if vstype != 'detect':
                cmd.extend(['-t', vstype])
            cmd.append(self.parent.get_raw_path())
            output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
            self.volume_source = 'multi'
        except Exception as e:
            # some bug in sleuthkit makes detection sometimes difficult, so we hack around it:
            if hasattr(e, 'output') and "(GPT or DOS at 0)" in e.output.decode(
            ) and vstype != 'gpt':
                self.vstype = 'gpt'
                # noinspection PyBroadException
                try:
                    logger.warning(
                        "Error in retrieving volume info: mmls couldn't decide between GPT and DOS, "
                        "choosing GPT for you. Use --vstype=dos to force DOS.",
                        exc_info=True)
                    cmd = ['mmls', '-t', 'gpt', self.parent.get_raw_path()]
                    output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
                    self.volume_source = 'multi'
                except Exception as e:
                    logger.exception("Failed executing mmls command")
                    raise SubsystemError(e)
            else:
                logger.exception("Failed executing mmls command")
                raise SubsystemError(e)

        output = output.split("Description", 1)[-1]
        for line in output.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                values = line.split(None, 5)

                # sometimes there are only 5 elements available
                description = ''
                index, slot, start, end, length = values[0:5]
                if len(values) > 5:
                    description = values[5]

                volume = self._make_subvolume(
                    index=self._format_index(int(index[:-1])),
                    offset=int(start) * self.disk.block_size,
                    size=int(length) * self.disk.block_size)
                volume.info['fsdescription'] = description
            except Exception:
                logger.exception("Error while parsing mmls output")
                continue

            if slot.lower() == 'meta':
                volume.flag = 'meta'
                logger.info(
                    "Found meta volume: block offset: {0}, length: {1}".format(
                        start, length))
            elif slot.lower().startswith('-----'):
                volume.flag = 'unalloc'
                logger.info(
                    "Found unallocated space: block offset: {0}, length: {1}".
                    format(start, length))
            else:
                volume.flag = 'alloc'
                if ":" in slot:
                    volume.slot = _util.determine_slot(*slot.split(':'))
                else:
                    volume.slot = _util.determine_slot(-1, slot)
                self._assign_disktype_data(volume)
                logger.info(
                    "Found allocated {2}: block offset: {0}, length: {1} ".
                    format(start, length, volume.info['fsdescription']))

            yield volume
Exemplo n.º 16
0
    def _detect_parted_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on parted."""

        # for some reason, parted does not properly return extended volume types in its machine
        # output, so we need to execute it twice.
        meta_volumes = []
        # noinspection PyBroadException
        try:
            output = _util.check_output_(
                ['parted', self.parent.get_raw_path(), 'print'],
                stdin=subprocess.PIPE)
            for line in output.splitlines():
                if 'extended' in line:
                    meta_volumes.append(int(line.split()[0]))
        except Exception:
            logger.exception("Failed executing parted command.")
            # skip detection of meta volumes

        # noinspection PyBroadException
        try:
            # parted does not support passing in the vstype. It either works, or it doesn't.
            cmd = [
                'parted',
                self.parent.get_raw_path(), '-sm', 'unit s', 'print free'
            ]
            output = _util.check_output_(cmd, stdin=subprocess.PIPE)
            self.volume_source = 'multi'
        except Exception as e:
            logger.exception("Failed executing parted command")
            raise SubsystemError(e)

        num = 0
        for line in output.splitlines():
            if line.startswith(
                    "Warning"
            ) or not line or ':' not in line or line.startswith(
                    self.parent.get_raw_path()):
                continue
            line = line[:-1]  # remove last ;
            try:
                slot, start, end, length, description = line.split(':', 4)
                if ':' in description:
                    description, label, flags = description.split(':', 2)
                else:
                    description, label, flags = description, '', ''

                try:
                    slot = int(slot)
                except ValueError:
                    continue

                volume = self._make_subvolume(
                    index=self._format_index(num),
                    offset=int(start[:-1]) *
                    self.disk.block_size,  # remove last s
                    size=int(length[:-1]) * self.disk.block_size)
                volume.info['fsdescription'] = description
                if label:
                    volume.info['label'] = label
                if flags:
                    volume.info['parted_flags'] = flags

                # TODO: detection of meta volumes

                if description == 'free':
                    volume.flag = 'unalloc'
                    logger.info(
                        "Found unallocated space: block offset: {0}, length: {1}"
                        .format(start[:-1], length[:-1]))
                elif slot in meta_volumes:
                    volume.flag = 'meta'
                    volume.slot = slot
                    logger.info(
                        "Found meta volume: block offset: {0}, length: {1}".
                        format(start[:-1], length[:-1]))
                else:
                    volume.flag = 'alloc'
                    volume.slot = slot
                    self._assign_disktype_data(volume)
                    logger.info(
                        "Found allocated {2}: block offset: {0}, length: {1} ".
                        format(start[:-1], length[:-1],
                               volume.info['fsdescription']))
            except AttributeError:
                logger.exception("Error while parsing parted output")
                continue

            num += 1

            yield volume
Exemplo n.º 17
0
    def mount(self, volume):
        """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is
        added to the subvolume set of this volume. Requires the user to enter the key manually.

        TODO: add support for :attr:`keys`

        :return: the Volume contained in the LUKS container, or None on failure.
        :raises NoLoopbackAvailableError: when no free loopback could be found
        :raises IncorrectFilesystemError: when this is not a LUKS volume
        :raises SubsystemError: when the underlying command fails
        """

        # Open a loopback device
        volume._find_loopback()

        # Check if this is a LUKS device
        # noinspection PyBroadException
        try:
            _util.check_call_(["cryptsetup", "isLuks", volume.loopback],
                              stderr=subprocess.STDOUT)
            # ret = 0 if isLuks
        except Exception:
            logger.warning("Not a LUKS volume")
            # clean the loopback device, we want this method to be clean as possible
            # noinspection PyBroadException
            try:
                volume._free_loopback()
            except Exception:
                pass
            raise IncorrectFilesystemError()

        try:
            extra_args = []
            key = None
            if volume.key:
                t, v = volume.key.split(':', 1)
                if t == 'p':  # passphrase
                    key = v
                elif t == 'f':  # key-file
                    extra_args = ['--key-file', v]
                elif t == 'm':  # master-key-file
                    extra_args = ['--master-key-file', v]
            else:
                logger.warning("No key material provided for %s", volume)
        except ValueError:
            logger.exception(
                "Invalid key material provided (%s) for %s. Expecting [arg]:[value]",
                volume.key, volume)
            volume._free_loopback()
            raise ArgumentError()

        # Open the LUKS container
        volume._paths['luks'] = 'image_mounter_luks_' + str(
            random.randint(10000, 99999))

        # noinspection PyBroadException
        try:
            cmd = [
                "cryptsetup", "luksOpen", volume.loopback,
                volume._paths['luks']
            ]
            cmd.extend(extra_args)
            if not volume.disk.read_write:
                cmd.insert(1, '-r')

            if key is not None:
                logger.debug('$ {0}'.format(' '.join(cmd)))
                # for py 3.2+, we could have used input=, but that doesn't exist in py2.7.
                p = subprocess.Popen(cmd,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                p.communicate(key.encode("utf-8"))
                p.wait()
                retcode = p.poll()
                if retcode:
                    raise KeyInvalidError()
            else:
                _util.check_call_(cmd)
        except ImageMounterError:
            del volume._paths['luks']
            volume._free_loopback()
            raise
        except Exception as e:
            del volume._paths['luks']
            volume._free_loopback()
            raise SubsystemError(e)

        size = None
        # noinspection PyBroadException
        try:
            result = _util.check_output_(
                ["cryptsetup", "status", volume._paths['luks']])
            for l in result.splitlines():
                if "size:" in l and "key" not in l:
                    size = int(
                        l.replace("size:", "").replace(
                            "sectors", "").strip()) * volume.disk.block_size
        except Exception:
            pass

        container = volume.volumes._make_single_subvolume(flag='alloc',
                                                          offset=0,
                                                          size=size)
        container.info['fsdescription'] = 'LUKS Volume'

        return container