예제 #1
0
    def checkFuse(self):
        """
        Check if command in self.mountproc is installed and user is part of
        group ``fuse``.

        Raises:
            exceptions.MountException:  if either command is not available or
                                        user is not in group fuse
        """
        logger.debug('Check fuse', self)
        if not tools.checkCommand(self.mountproc):
            logger.debug('%s is missing' % self.mountproc, self)
            raise MountException(
                _('%(proc)s not found. Please install e.g. %(install_command)s'
                  ) %
                {
                    'proc': self.mountproc,
                    'install_command': "'apt-get install %s'" % self.mountproc
                })
        if self.CHECK_FUSE_GROUP:
            user = self.config.user()
            try:
                fuse_grp_members = grp.getgrnam('fuse')[3]
            except KeyError:
                #group fuse doesn't exist. So most likely it isn't used by this distribution
                logger.debug("Group fuse doesn't exist. Skip test", self)
                return
            if not user in fuse_grp_members:
                logger.debug('User %s is not in group fuse' % user, self)
                raise MountException(
                    _('%(user)s is not member of group \'fuse\'.\n '
                      'Run \'sudo adduser %(user)s fuse\'. To apply '
                      'changes logout and login again.\nLook at '
                      '\'man backintime\' for further instructions.') %
                    {'user': user})
예제 #2
0
 def isConfigured(self):
     """
     check if encfs config file exist. If not and if we are in settingsdialog
     ask for password confirmation. _mount will then create a new config
     """
     cfg = self.configFile()
     if os.path.isfile(cfg):
         logger.debug('Found encfs config in %s' % cfg, self)
         return True
     else:
         logger.debug('No encfs config in %s' % cfg, self)
         msg = _('Config for encrypted folder not found.')
         if not self.tmp_mount:
             raise MountException(msg)
         else:
             if not self.config.askQuestion(
                     msg + _('\nCreate a new encrypted folder?')):
                 raise MountException(_('Cancel'))
             else:
                 pw = password.Password(self.config)
                 password_confirm = pw.passwordFromUser(
                     self.parent, prompt=_('Please confirm password'))
                 if self.password == password_confirm:
                     return False
                 else:
                     raise MountException(_('Password doesn\'t match'))
예제 #3
0
    def checkRemoteFolder(self):
        """
        Check the remote path. If the remote path doesn't exist this will create
        it. If it already exist this will check, that it is a folder and has
        correct permissions.

        Raises:
            exceptions.MountException:  if remote path couldn't be created or
                                        doesn't have correct permissions.
        """
        logger.debug('Check remote folder', self)
        cmd = 'd=0;'
        cmd += 'test -e "%s" || d=1;' % self.path  #path doesn't exist. set d=1 to indicate
        cmd += 'test $d -eq 1 && mkdir "%s"; err=$?;' % self.path  #create path, get errorcode from mkdir
        cmd += 'test $d -eq 1 && exit $err;'  #return errorcode from mkdir
        cmd += 'test -d "%s" || exit 11;' % self.path  #path is no directory
        cmd += 'test -w "%s" || exit 12;' % self.path  #path is not writable
        cmd += 'test -x "%s" || exit 13;' % self.path  #path is not executable
        cmd += 'exit 20'  #everything is fine
        ssh = self.config.sshCommand(
            cmd=[cmd],
            custom_args=['-p', str(self.port), self.user_host],
            port=False,
            cipher=False,
            user_host=False,
            nice=False,
            ionice=False,
            profile_id=self.profile_id)
        logger.debug('Call command: %s' % ' '.join(ssh), self)
        proc = subprocess.Popen(ssh,
                                stdout=subprocess.DEVNULL,
                                stderr=subprocess.DEVNULL)
        proc.communicate()
        if proc.returncode:
            logger.debug('Command returncode: %s' % proc.returncode, self)
            if proc.returncode == 20:
                #clean exit
                pass
            elif proc.returncode == 11:
                raise MountException(
                    _('Remote path exists but is not a directory:\n %s') %
                    self.path)
            elif proc.returncode == 12:
                raise MountException(
                    _('Remote path is not writable:\n %s') % self.path)
            elif proc.returncode == 13:
                raise MountException(
                    _('Remote path is not executable:\n %s') % self.path)
            else:
                raise MountException(
                    _('Couldn\'t create remote path:\n %s') % self.path)
        else:
            #returncode is 0
            logger.info('Create remote folder %s' % self.path, self)
예제 #4
0
    def checkLogin(self):
        """
        Try to login to remote host with public/private-key-method (passwordless).

        Raises:
            exceptions.MountException:  if login failed
        """
        logger.debug('Check login', self)
        ssh = self.config.sshCommand(cmd=['echo', '"Hello"'],
                                     custom_args=[
                                         '-o',
                                         'PreferredAuthentications=publickey',
                                         '-p',
                                         str(self.port), self.user_host
                                     ],
                                     port=False,
                                     cipher=False,
                                     user_host=False,
                                     nice=False,
                                     ionice=False,
                                     profile_id=self.profile_id)
        proc = subprocess.Popen(ssh,
                                stdout=subprocess.DEVNULL,
                                stderr=subprocess.PIPE,
                                universal_newlines=True)
        err = proc.communicate()[1]
        if proc.returncode:
            raise MountException(
                _('Password-less authentication for %(user)s@%(host)s '
                  'failed. Look at \'man backintime\' for further '
                  'instructions.') % {
                      'user': self.user,
                      'host': self.host
                  } + '\n\n' + err)
예제 #5
0
    def checkPingHost(self):
        """
        Check if the remote host is online. Other than methods name may let suppose
        this does not use Ping (``ICMP``) but try to open a connection to
        the configured port on the remote host. In this way it will even work
        on remote hosts which have ``ICMP`` disabled.

        If connection failed it will retry five times before failing.

        Raises:
            exceptions.MountException:  if connection failed most probably
                                        because remote host is offline
        """
        if not self.config.sshCheckPingHost(self.profile_id):
            return
        logger.debug('Check ping host', self)
        count = 0
        while count < 5:
            try:
                with socket.create_connection((self.host, self.port),
                                              2.0) as s:
                    result = s.connect_ex(s.getpeername())
            except:
                result = -1
            if result == 0:
                logger.debug('Host %s is available' % self.host, self)
                return
            logger.debug('Could not ping host %s. Try again' % self.host, self)
            count += 1
            sleep(0.2)
        if result != 0:
            logger.debug('Failed pinging host %s' % self.host, self)
            raise MountException(
                _('Ping %s failed. Host is down or wrong address.') %
                self.host)
예제 #6
0
    def _mount(self):
        """
        mount the service
        """
        if self.password is None:
            self.password = self.config.password(self.parent, self.profile_id,
                                                 self.mode)
        logger.debug('Provide password through temp FIFO', self)
        thread = password_ipc.TempPasswordThread(self.password)
        env = self.env()
        env['ASKPASS_TEMP'] = thread.temp_file
        thread.start()

        encfs = [self.mountproc, '--extpass=backintime-askpass']
        if self.reverse:
            encfs += ['--reverse']
        if not self.isConfigured():
            encfs += ['--standard']
        encfs += [self.path, self.currentMountpoint]
        logger.debug('Call mount command: %s' % ' '.join(encfs), self)

        proc = subprocess.Popen(encfs,
                                env=env,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                universal_newlines=True)
        output = proc.communicate()[0]
        self.backupConfig()
        if proc.returncode:
            raise MountException(_('Can\'t mount \'%(command)s\':\n\n%(error)s') \
                                    % {'command': ' '.join(encfs), 'error': output})

        thread.stop()
예제 #7
0
    def _mount(self):
        """
        Backend mount method. This will call ``sshfs`` to mount the remote path.

        Raises:
            exceptions.MountException:  if mount wasn't successful
        """
        sshfs = [self.mountproc]
        sshfs += self.config.sshDefaultArgs(self.profile_id)
        sshfs += ['-p', str(self.port)]
        if not self.cipher == 'default':
            sshfs.extend(['-o', 'Ciphers=%s' % self.cipher])
        sshfs.extend([
            '-o', 'idmap=user', '-o', 'cache_dir_timeout=2', '-o',
            'cache_stat_timeout=2'
        ])

        sshfs.extend([self.user_host_path, self.currentMountpoint])
        #bugfix: sshfs doesn't mount if locale in LC_ALL is not available on remote host
        #LANG or other envirnoment variable are no problem.
        env = os.environ.copy()
        if 'LC_ALL' in list(env.keys()):
            env['LC_ALL'] = 'C'
        logger.debug('Call mount command: %s' % ' '.join(sshfs), self)
        proc = subprocess.Popen(sshfs,
                                env=env,
                                stdout=subprocess.DEVNULL,
                                stderr=subprocess.PIPE,
                                universal_newlines=True)
        err = proc.communicate()[1]
        if proc.returncode:
            raise MountException(
                _('Can\'t mount %s') % ' '.join(sshfs) + '\n\n' + err)
예제 #8
0
    def checkCipher(self):
        """
        Try to login to remote host with the choosen cipher. This should make
        sure both `localhost` and the remote host support the choosen cipher.

        Raises:
            exceptions.MountException:  if login with the cipher failed
        """
        if not self.cipher == 'default':
            logger.debug('Check cipher', self)
            ssh = self.config.sshCommand(cmd = ['echo', '"Hello"'],
                                          custom_args = ['-o', 'Ciphers=%s' % self.cipher,
                                                         '-p', str(self.port),
                                                         self.user_host],
                                          port = False,
                                          cipher = False,
                                          user_host = False,
                                          nice = False,
                                          ionice = False,
                                          profile_id = self.profile_id)
            proc = subprocess.Popen(ssh,
                                    stdout=subprocess.DEVNULL,
                                    stderr=subprocess.PIPE,
                                    universal_newlines = True)
            err = proc.communicate()[1]
            if proc.returncode:
                logger.debug('Ciper %s is not supported' %self.config.SSH_CIPHERS[self.cipher], self)
                raise MountException(_('Cipher %(cipher)s failed for %(host)s:\n%(err)s')
                                      % {'cipher' : self.config.SSH_CIPHERS[self.cipher], 'host' : self.host, 'err' : err})
예제 #9
0
    def mountProcessLockAcquire(self, timeout=60):
        """
        Create a short term lock only for blocking other processes changing
        mounts at the same time.

        Args:
            timeout (int):  wait ``timeout`` seconds before fail acquiring
                            the lock

        Raises:
            exceptions.MountException:
                            if timed out
        """
        lock_path = self.mount_root
        lockSuffix = '.lock'
        lock = os.path.join(lock_path, self.pid + lockSuffix)
        count = 0
        while self.checkLocks(lock_path, lockSuffix):
            count += 1
            if count == timeout:
                raise MountException(_('Mountprocess lock timeout'))
            sleep(1)

        logger.debug('Acquire mountprocess lock %s' % lock, self)
        with open(lock, 'w') as f:
            f.write(self.pid)
예제 #10
0
 def _umount(self):
     """
     umount the service
     """
     try:
         subprocess.check_call(['fusermount', '-u', self.mountpoint])
     except subprocess.CalledProcessError:
         raise MountException( _('Can\'t unmount sshfs %s') % self.mountpoint)
예제 #11
0
    def startSshAgent(self):
        """
        Start a new ``ssh-agent`` if it is not already running.

        Raises:
            exceptions.MountException:  if starting ``ssh-agent`` failed
        """
        SOCK = 'SSH_AUTH_SOCK'
        PID = 'SSH_AGENT_PID'
        if os.getenv(SOCK, '') and os.getenv(PID, ''):
            logger.debug('ssh-agent already running. Skip starting a new one.',
                         self)
            return

        sshAgent = tools.which('ssh-agent')
        if not sshAgent:
            raise MountException(
                'ssh-agent not found. Please make sure it is installed.')
        if isinstance(sshAgent, str):
            sshAgent = [
                sshAgent,
            ]

        sa = subprocess.Popen(sshAgent,
                              stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE,
                              universal_newlines=True)
        out, err = sa.communicate()

        if sa.returncode:
            raise MountException('Failed to start ssh-agent: [{}] {}'.format(
                sa.returncode, err))

        m = re.match(
            r'.*{}(?:=|\s)([^;]+);.*{}(?:=|\s)(\d+);'.format(SOCK, PID), out,
            re.DOTALL | re.MULTILINE)
        if m:
            logger.debug(
                'ssh-agent started successful: {}={} | {}={}'.format(
                    SOCK, m.group(1), PID, m.group(2)), self)
            os.environ[SOCK] = m.group(1)
            os.environ[PID] = m.group(2)
            atexit.register(os.kill, int(m.group(2)), signal.SIGKILL)
        else:
            raise MountException(
                'No matching output from ssh-agent: {} | {}'.format(out, err))
예제 #12
0
파일: mount.py 프로젝트: utajum/backintime
 def is_mounted(self):
     """
     return True if path is is already mounted
     """
     if os.path.ismount(self.mountpoint):
         return True
     else:
         if os.listdir(self.mountpoint):
             raise MountException(
                 _('mountpoint %s not empty.') % self.mountpoint)
         return False
예제 #13
0
 def maxArg():
     if retry:
         raise MountException("Checking commands on remote host didn't return any output. "
                              "We already checked the maximum argument lenght but it seem like "
                              "there is an other problem")
     logger.warning('Looks like the command was to long for remote SSHd. We will test max arg length now and retry.',
                    self)
     import sshMaxArg
     mid = sshMaxArg.test_ssh_max_arg(self.user_host)
     sshMaxArg.reportResult(self.host, mid)
     self.config.set_ssh_max_arg_length(mid, self.profile_id)
     return self.check_remote_commands(retry = True)
예제 #14
0
 def check_remote_folder(self):
     """
     check if remote folder exists and is write- and executable.
     Create folder if it doesn't exist.
     """
     logger.debug('Check remote folder', self)
     cmd  = 'd=0;'
     cmd += 'test -e %s || d=1;' % self.path                 #path doesn't exist. set d=1 to indicate
     cmd += 'test $d -eq 1 && mkdir %s; err=$?;' % self.path #create path, get errorcode from mkdir
     cmd += 'test $d -eq 1 && exit $err;'                    #return errorcode from mkdir
     cmd += 'test -d %s || exit 11;' % self.path #path is no directory
     cmd += 'test -w %s || exit 12;' % self.path #path is not writeable
     cmd += 'test -x %s || exit 13;' % self.path #path is not executable
     cmd += 'exit 20'                             #everything is fine
     ssh = ['ssh']
     ssh.extend(self.ssh_options + [self.user_host])
     ssh.extend(self.config.ssh_prefix_cmd(self.profile_id, cmd_type = list))
     ssh.extend([cmd])
     logger.debug('Call command: %s' %' '.join(ssh), self)
     try:
         subprocess.check_call(ssh,
                               stdout=subprocess.DEVNULL)
     except subprocess.CalledProcessError as ex:
         logger.debug('Command returncode: %s' %ex.returncode, self)
         if ex.returncode == 20:
             #clean exit
             pass
         elif ex.returncode == 11:
             raise MountException( _('Remote path exists but is not a directory:\n %s') % self.path)
         elif ex.returncode == 12:
             raise MountException( _('Remote path is not writeable:\n %s') % self.path)
         elif ex.returncode == 13:
             raise MountException( _('Remote path is not executable:\n %s') % self.path)
         else:
             raise MountException( _('Couldn\'t create remote path:\n %s') % self.path)
     else:
         #returncode is 0
         logger.info('Create remote folder %s' %self.path, self)
예제 #15
0
 def check_fuse(self):
     """
     check if sshfs is installed and user is part of group fuse
     """
     logger.debug('Check fuse', self)
     if not tools.check_command('sshfs'):
         logger.debug('sshfs is missing', self)
         raise MountException( _('sshfs not found. Please install e.g. \'apt-get install sshfs\'') )
     if self.CHECK_FUSE_GROUP:
         user = self.config.get_user()
         try:
             fuse_grp_members = grp.getgrnam('fuse')[3]
         except KeyError:
             #group fuse doesn't exist. So most likely it isn't used by this distribution
             logger.debug("Group fuse doesn't exist. Skip test", self)
             return
         if not user in fuse_grp_members:
             logger.debug('User %s is not in group fuse' %user, self)
             raise MountException( _('%(user)s is not member of group \'fuse\'.\n '
                                     'Run \'sudo adduser %(user)s fuse\'. To apply '
                                     'changes logout and login again.\nLook at '
                                     '\'man backintime\' for further instructions.')
                                     % {'user': user})
예제 #16
0
    def _umount(self):
        """
        Unmount with ``fusermount -u`` for fuse based backends. This **can** be
        overwritten by backends which subclasses :py:class:`MountControl`.

        Raises:
            exceptions.MountException:  if unmount failed
        """
        try:
            subprocess.check_call(['fusermount', '-u', self.currentMountpoint])
        except subprocess.CalledProcessError:
            raise MountException(_('Can\'t unmount %(proc)s from %(mountpoint)s')
                                  %{'proc': self.mountproc,
                                    'mountpoint': self.currentMountpoint})
예제 #17
0
 def check_login(self):
     """
     check passwordless authentication to host
     """
     logger.debug('Check login', self)
     ssh = ['ssh', '-o', 'PreferredAuthentications=publickey']
     ssh.extend(self.ssh_options + [self.user_host])
     ssh.extend(self.config.ssh_prefix_cmd(self.profile_id, cmd_type = list))
     ssh.extend(['echo', '"Hello"'])
     try:
         subprocess.check_call(ssh, stdout=subprocess.DEVNULL)
     except subprocess.CalledProcessError:
         raise MountException( _('Password-less authentication for %(user)s@%(host)s '
                                 'failed. Look at \'man backintime\' for further '
                                 'instructions.')  % {'user' : self.user, 'host' : self.host})
예제 #18
0
 def check_known_hosts(self):
     """
     check ssh_known_hosts
     """
     logger.debug('Check known hosts file', self)
     for host in (self.host, '[%s]:%s' % (self.host, self.port)):
         proc = subprocess.Popen(['ssh-keygen', '-F', host],
                                 stdout=subprocess.PIPE,
                                 universal_newlines = True)
         output = proc.communicate()[0] #subprocess.check_output doesn't exist in Python 2.6 (Debian squeeze default)
         if output.find('Host %s found' % host) >= 0:
             logger.debug('Host %s was found in known hosts file' % host, self)
             return True
     logger.debug('Host %s is not in known hosts file' %self.host, self)
     raise MountException( _('%s not found in ssh_known_hosts.') % self.host)
예제 #19
0
 def checkVersion(self):
     """
     check encfs version.
     1.7.2 had a bug with --reverse that will create corrupt files
     """
     logger.debug('Check version', self)
     if self.reverse:
         proc = subprocess.Popen(['encfs', '--version'],
                                 stdout = subprocess.PIPE,
                                 stderr = subprocess.STDOUT,
                                 universal_newlines = True)
         output = proc.communicate()[0]
         m = re.search(r'(\d\.\d\.\d)', output)
         if m and StrictVersion(m.group(1)) <= StrictVersion('1.7.2'):
             logger.debug('Wrong encfs version %s' %m.group(1), self)
             raise MountException(_('encfs version 1.7.2 and before has a bug with option --reverse. Please update encfs'))
예제 #20
0
파일: mount.py 프로젝트: utajum/backintime
    def mountprocess_lock_acquire(self, timeout=60):
        """
        block while an other process is mounting or unmounting
        """
        lock_path = self.mount_root
        lock_suffix = '.lock'
        lock = os.path.join(lock_path, self.pid + lock_suffix)
        count = 0
        while self.check_locks(lock_path, lock_suffix):
            count += 1
            if count == timeout:
                raise MountException(_('Mountprocess lock timeout'))
            sleep(1)

        logger.debug('Acquire mountprocess lock %s' % lock, self)
        with open(lock, 'w') as f:
            f.write(self.pid)
예제 #21
0
    def mounted(self):
        """
        Check if the mountpoint is already mounted.

        Returns:
            bool:   ``True`` if mountpoint is mounted

        Raises:
            exceptions.MountException:
                    if mountpoint is not mounted but also not empty
        """
        if os.path.ismount(self.currentMountpoint):
            return True
        else:
            if os.listdir(self.currentMountpoint):
                raise MountException(_('mountpoint %s not empty.') % self.currentMountpoint)
            return False
예제 #22
0
 def check_cipher(self):
     """
     check if both host and localhost support cipher
     """
     if not self.cipher == 'default':
         logger.debug('Check cipher', self)
         ssh = ['ssh']
         ssh.extend(['-o', 'Ciphers=%s' % self.cipher])
         ssh.extend(self.ssh_options + [self.user_host])
         ssh.extend(self.config.ssh_prefix_cmd(self.profile_id, cmd_type = list))
         ssh.extend(['echo', '"Hello"'])
         proc = subprocess.Popen(ssh,
                                 stdout=subprocess.DEVNULL,
                                 stderr=subprocess.PIPE,
                                 universal_newlines = True)
         err = proc.communicate()[1]
         if proc.returncode:
             logger.debug('Ciper %s is not supported' %self.config.SSH_CIPHERS[self.cipher], self)
             raise MountException( _('Cipher %(cipher)s failed for %(host)s:\n%(err)s')
                                   % {'cipher' : self.config.SSH_CIPHERS[self.cipher], 'host' : self.host, 'err' : err})
예제 #23
0
 def _mount(self):
     """
     mount the service
     """
     sshfs = ['sshfs'] + self.ssh_options
     if not self.cipher == 'default':
         sshfs.extend(['-o', 'Ciphers=%s' % self.cipher])
     sshfs.extend(['-o', 'idmap=user'])
     sshfs.extend([self.user_host_path, self.mountpoint])
     #bugfix: sshfs doesn't mount if locale in LC_ALL is not available on remote host
     #LANG or other envirnoment variable are no problem.
     env = os.environ.copy()
     if 'LC_ALL' in list(env.keys()):
         env['LC_ALL'] = 'C'
     logger.debug('Call mount command: %s'
                  %' '.join(sshfs),
                  self)
     try:
         subprocess.check_call(sshfs, env = env)
     except subprocess.CalledProcessError:
         raise MountException( _('Can\'t mount %s') % ' '.join(sshfs))
예제 #24
0
 def check_ping_host(self):
     """
     connect to remote port and check if it is open
     """
     logger.debug('Check ping host', self)
     count = 0
     while count < 5:
         try:
             with socket.create_connection((self.host, self.port), 2.0) as s:
                 result = s.connect_ex(s.getpeername())
         except:
             result = -1
         if result == 0:
             logger.debug('Host %s is available' %self.host, self)
             return
         logger.debug('Could not ping host %s. Try again' %self.host, self)
         count += 1
         sleep(0.2)
     if result != 0:
         logger.debug('Failed pinging host %s' %self.host, self)
         raise MountException( _('Ping %s failed. Host is down or wrong address.') % self.host)
예제 #25
0
    def checkRemoteCommands(self, retry=False):
        """
        Try out all relevant commands used by `Back In Time` on the remote host
        to make sure snapshots will be successful with the remote host.
        This will also check that hard-links are supported on the remote host.

        This check can be disabled with :py:func:`config.Config.sshCheckCommands`

        Args:
            retry (bool):               retry to run the commands if it failed
                                        because the command string was to long

        Raises:
            exceptions.MountException:  if a command is not supported on
                                        remote host or if hard-links are not
                                        supported
        """
        if not self.config.sshCheckCommands():
            return
        logger.debug('Check remote commands', self)

        def maxArg():
            if retry:
                raise MountException(
                    "Checking commands on remote host didn't return any output. "
                    "We already checked the maximum argument lenght but it seem like "
                    "there is an other problem")
            logger.warning(
                'Looks like the command was to long for remote SSHd. We will test max arg length now and retry.',
                self)
            import sshMaxArg
            mid = sshMaxArg.maxArgLength(self.config)
            sshMaxArg.reportResult(self.host, mid)
            self.config.setSshMaxArgLength(mid, self.profile_id)
            return self.checkRemoteCommands(retry=True)

        remote_tmp_dir_1 = os.path.join(self.path, 'tmp_%s' % self.randomId())
        remote_tmp_dir_2 = os.path.join(self.path, 'tmp_%s' % self.randomId())
        with tempfile.TemporaryDirectory() as tmp:
            tmp_file = os.path.join(tmp, 'a')
            with open(tmp_file, 'wt') as f:
                f.write('foo')

            #check rsync
            rsync1 = tools.rsyncPrefix(self.config,
                                       no_perms=False,
                                       progress=False)
            rsync1.append(tmp_file)
            rsync1.append('%s@%s:"%s"/' %
                          (self.user, tools.escapeIPv6Address(
                              self.host), remote_tmp_dir_1))

            #check remote rsync hard-link support
            rsync2 = tools.rsyncPrefix(self.config,
                                       no_perms=False,
                                       progress=False)
            rsync2.append('--link-dest=../%s' %
                          os.path.basename(remote_tmp_dir_1))
            rsync2.append(tmp_file)
            rsync2.append('%s@%s:"%s"/' %
                          (self.user, tools.escapeIPv6Address(
                              self.host), remote_tmp_dir_2))

            for cmd in (rsync1, rsync2):
                logger.debug('Check rsync command: %s' % cmd, self)

                proc = subprocess.Popen(cmd,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        universal_newlines=True)
                out, err = proc.communicate()
                if err or proc.returncode:
                    logger.debug('rsync command returned error: %s' % err,
                                 self)
                    raise MountException(
                        _('Remote host %(host)s doesn\'t support \'%(command)s\':\n'
                          '%(err)s\nLook at \'man backintime\' for further instructions'
                          ) % {
                              'host': self.host,
                              'command': cmd,
                              'err': err
                          })

        #check cp chmod find and rm
        head = 'tmp1="%s"; tmp2="%s"; ' % (remote_tmp_dir_1, remote_tmp_dir_2)
        #first define a function to clean up and exit
        head += 'cleanup(){ '
        head += 'test -e "$tmp1/a" && rm "$tmp1/a" >/dev/null 2>&1; '
        head += 'test -e "$tmp2/a" && rm "$tmp2/a" >/dev/null 2>&1; '
        head += 'test -e smr.lock && rm smr.lock >/dev/null 2>&1; '
        head += 'test -e "$tmp1" && rmdir "$tmp1" >/dev/null 2>&1; '
        head += 'test -e "$tmp2" && rmdir "$tmp2" >/dev/null 2>&1; '
        head += 'test -n "$tmp3" && test -e "$tmp3" && rmdir "$tmp3" >/dev/null 2>&1; '
        head += 'exit $1; }; '
        tail = []

        #list inodes
        cmd = 'ls -i "$tmp1/a"; ls -i "$tmp2/a"; '
        tail.append(cmd)
        #try nice -n 19
        if self.nice:
            cmd = 'echo \"nice -n 19\"; nice -n 19 true >/dev/null; err_nice=$?; '
            cmd += 'test $err_nice -ne 0 && cleanup $err_nice; '
            tail.append(cmd)
        #try ionice -c2 -n7
        if self.ionice:
            cmd = 'echo \"ionice -c2 -n7\"; ionice -c2 -n7 true >/dev/null; err_nice=$?; '
            cmd += 'test $err_nice -ne 0 && cleanup $err_nice; '
            tail.append(cmd)
        #try nocache
        if self.nocache:
            cmd = 'echo \"nocache\"; nocache true >/dev/null; err_nocache=$?; '
            cmd += 'test $err_nocache -ne 0 && cleanup $err_nocache; '
            tail.append(cmd)
        #try screen, bash and flock used by smart-remove running in background
        if self.config.smartRemoveRunRemoteInBackground(self.profile_id):
            cmd = 'echo \"screen -d -m bash -c ...\"; screen -d -m bash -c \"true\" >/dev/null; err_screen=$?; '
            cmd += 'test $err_screen -ne 0 && cleanup $err_screen; '
            tail.append(cmd)
            cmd = 'echo \"(flock -x 9) 9>smr.lock\"; bash -c \"(flock -x 9) 9>smr.lock\" >/dev/null; err_flock=$?; '
            cmd += 'test $err_flock -ne 0 && cleanup $err_flock; '
            tail.append(cmd)
            cmd = 'echo \"rmdir \$(mktemp -d)\"; tmp3=$(mktemp -d); test -z "$tmp3" && cleanup 1; rmdir $tmp3 >/dev/null; err_rmdir=$?; '
            cmd += 'test $err_rmdir -ne 0 && cleanup $err_rmdir; '
            tail.append(cmd)
        #if we end up here, everything should be fine
        cmd = 'echo \"done\"; cleanup 0'
        tail.append(cmd)

        maxLength = self.config.sshMaxArgLength(self.profile_id)
        additionalChars = len('echo ""') + len(
            self.config.sshPrefixCmd(self.profile_id, cmd_type=str))

        output = ''
        err = ''
        returncode = 0
        for cmd in tools.splitCommands(tail,
                                       head=head,
                                       maxLength=maxLength - additionalChars):
            if cmd.endswith('; '):
                cmd += 'echo ""'
            c = self.config.sshCommand(
                cmd=[cmd],
                custom_args=['-p', str(self.port), self.user_host],
                port=False,
                user_host=False,
                nice=False,
                ionice=False,
                profile_id=self.profile_id)
            try:
                logger.debug('Call command: %s' % ' '.join(c), self)
                proc = subprocess.Popen(c,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        universal_newlines=True)
                ret = proc.communicate()
            except OSError as e:
                #Argument list too long
                if e.errno == 7:
                    logger.debug('Argument list too log (Python exception)',
                                 self)
                    return maxArg()
                else:
                    raise
            logger.debug('Command stdout: %s' % ret[0], self)
            logger.debug('Command stderr: %s' % ret[1], self)
            logger.debug('Command returncode: %s' % proc.returncode, self)
            output += ret[0].strip('\n') + '\n'
            err += ret[1].strip('\n') + '\n'
            returncode += proc.returncode
            if proc.returncode:
                break

        output_split = output.strip('\n').split('\n')

        while True:
            if output_split and not output_split[-1]:
                output_split = output_split[:-1]
            else:
                break

        if not output_split:
            return maxArg()

        if returncode or not output_split[-1].startswith('done'):
            for command in ('rm', 'nice', 'ionice', 'nocache', 'screen',
                            '(flock'):
                if output_split[-1].startswith(command):
                    raise MountException(
                        _('Remote host %(host)s doesn\'t support \'%(command)s\':\n'
                          '%(err)s\nLook at \'man backintime\' for further instructions'
                          ) % {
                              'host': self.host,
                              'command': output_split[-1],
                              'err': err
                          })
            raise MountException(
                _('Check commands on host %(host)s returned unknown error:\n'
                  '%(err)s\nLook at \'man backintime\' for further instructions'
                  ) % {
                      'host': self.host,
                      'err': err
                  })

        inodes = []
        for tmp in (remote_tmp_dir_1, remote_tmp_dir_2):
            for line in output_split:
                m = re.match(r'^(\d+).*?%s' % tmp, line)
                if m:
                    inodes.append(m.group(1))

        logger.debug('remote inodes: ' + ' | '.join(inodes), self)
        if len(inodes) == 2 and inodes[0] != inodes[1]:
            raise MountException(
                _('Remote host %s doesn\'t support hardlinks') % self.host)
예제 #26
0
    def unlockSshAgent(self, force=False):
        """
        Unlock the private key in ``ssh-agent`` which will provide it for
        all other commands. The password to unlock the key will be provided
        by ``backintime-askpass``.

        Args:
            force (bool):               force to unlock the key by removing it
                                        first and add it again to make sure,
                                        the given values are correct

        Raises:
            exceptions.MountException:  if unlock failed
        """
        self.startSshAgent()

        env = os.environ.copy()
        env['SSH_ASKPASS'] = '******'
        env['ASKPASS_PROFILE_ID'] = self.profile_id
        env['ASKPASS_MODE'] = self.mode

        if force:
            #remove private key first so we can check if the given password is valid
            logger.debug(
                'Remove private key %s from ssh agent' % self.private_key_file,
                self)
            proc = subprocess.Popen(['ssh-add', '-d', self.private_key_file],
                                    stdin=subprocess.DEVNULL,
                                    stdout=subprocess.DEVNULL,
                                    stderr=subprocess.DEVNULL,
                                    universal_newlines=True)
            proc.communicate()

        proc = subprocess.Popen(['ssh-add', '-l'],
                                stdout=subprocess.PIPE,
                                universal_newlines=True)
        output = proc.communicate()[0]
        if force or not output.find(self.private_key_fingerprint) >= 0:
            logger.debug(
                'Add private key %s to ssh agent' % self.private_key_file,
                self)
            password_available = any([
                self.config.passwordSave(self.profile_id),
                self.config.passwordUseCache(self.profile_id),
                not self.password is None
            ])
            logger.debug('Password available: %s' % password_available, self)
            if not password_available and not tools.checkXServer():
                #we need to unlink stdin from ssh-add in order to make it
                #use our own backintime-askpass.
                #But because of this we can NOT use getpass inside backintime-askpass
                #if password is not saved and there is no x-server.
                #So, let's just keep ssh-add asking for the password in that case.
                alarm = tools.Alarm()
                alarm.start(10)
                try:
                    proc = subprocess.call(['ssh-add', self.private_key_file])
                    alarm.stop()
                except tools.Timeout:
                    pass
            else:
                if self.password:
                    logger.debug('Provide password through temp FIFO', self)
                    thread = password_ipc.TempPasswordThread(self.password)
                    env['ASKPASS_TEMP'] = thread.temp_file
                    thread.start()

                proc = subprocess.Popen(['ssh-add', self.private_key_file],
                                        stdin=subprocess.PIPE,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        env=env,
                                        preexec_fn=os.setsid,
                                        universal_newlines=True)
                output, error = proc.communicate()
                if proc.returncode:
                    logger.error(
                        'Failed to unlock SSH private key %s: %s' %
                        (self.private_key_file, error), self)

                if self.password:
                    thread.stop()

            proc = subprocess.Popen(['ssh-add', '-l'],
                                    stdout=subprocess.PIPE,
                                    universal_newlines=True)
            output = proc.communicate()[0]
            if not output.find(self.private_key_fingerprint) >= 0:
                logger.debug(
                    'Was not able to unlock private key %s' %
                    self.private_key_file, self)
                raise MountException(
                    _('Could not unlock ssh private key. Wrong password '
                      'or password not available for cron.'))
        else:
            logger.debug(
                'Private key %s is already unlocked in ssh agent' %
                self.private_key_file, self)
예제 #27
0
 def test_mount_exception(self, takeSnapshot, mount, sleep):
     mount.side_effect = MountException()
     takeSnapshot.return_value = [True, False]
     self.assertFalse(self.sn.backup())
     self.assertFalse(takeSnapshot.called)
예제 #28
0
 def test_umount_exception(self, takeSnapshot, umount, sleep):
     umount.side_effect = MountException()
     takeSnapshot.return_value = [True, False]
     self.assertTrue(self.sn.backup())
예제 #29
0
    def check_remote_commands(self, retry = False):
        """
        try all relevant commands for take_snapshot on remote host.
        specialy embedded Linux devices using 'BusyBox' sometimes doesn't
        support everything that is need to run backintime.
        also check for hardlink-support on remote host.
        """
        logger.debug('Check remote commands', self)
        def maxArg():
            if retry:
                raise MountException("Checking commands on remote host didn't return any output. "
                                     "We already checked the maximum argument lenght but it seem like "
                                     "there is an other problem")
            logger.warning('Looks like the command was to long for remote SSHd. We will test max arg length now and retry.',
                           self)
            import sshMaxArg
            mid = sshMaxArg.test_ssh_max_arg(self.user_host)
            sshMaxArg.reportResult(self.host, mid)
            self.config.set_ssh_max_arg_length(mid, self.profile_id)
            return self.check_remote_commands(retry = True)

        #check rsync
        tmp_file = tempfile.mkstemp()[1]
        rsync = tools.get_rsync_prefix( self.config ) + ' --dry-run --chmod=Du+wx %s ' % tmp_file
        rsync += '"%s@%s:%s"' % (self.user, self.host, self.path)
        logger.debug('Check rsync command: %s' %rsync, self)

        #use os.system for compatiblity with snapshots.py
        err = os.system(rsync)
        if err:
            logger.debug('Rsync command returnd error: %s' %err, self)
            os.remove(tmp_file)
            raise MountException( _('Remote host %(host)s doesn\'t support \'%(command)s\':\n'
                                    '%(err)s\nLook at \'man backintime\' for further instructions')
                                    % {'host' : self.host, 'command' : rsync, 'err' : err})
        os.remove(tmp_file)

        #check cp chmod find and rm
        remote_tmp_dir = os.path.join(self.path, 'tmp_%s' % self.random_id())
        head  = 'tmp=%s ; ' % remote_tmp_dir
        #first define a function to clean up and exit
        head += 'cleanup(){ '
        head += 'test -e $tmp/a && rm $tmp/a >/dev/null 2>&1; '
        head += 'test -e $tmp/b && rm $tmp/b >/dev/null 2>&1; '
        head += 'test -e smr.lock && rm smr.lock >/dev/null 2>&1; '
        head += 'test -e $tmp && rmdir $tmp >/dev/null 2>&1; '
        head += 'exit $1; }; '
        tail = []
        #create tmp_RANDOM dir and file a
        cmd  = 'test -e $tmp || mkdir $tmp; touch $tmp/a; '
        tail.append(cmd)

        #try to create hardlink b from a
        cmd  = 'echo \"cp -aRl SOURCE DEST\"; cp -aRl $tmp/a $tmp/b >/dev/null; err_cp=$?; '
        cmd += 'test $err_cp -ne 0 && cleanup $err_cp; '
        tail.append(cmd)
        #list inodes of a and b
        cmd  = 'ls -i $tmp/a; ls -i $tmp/b; '
        tail.append(cmd)
        #try to chmod
        cmd  = 'echo \"chmod u+rw FILE\"; chmod u+rw $tmp/a >/dev/null; err_chmod=$?; '
        cmd += 'test $err_chmod -ne 0 && cleanup $err_chmod; '
        tail.append(cmd)
        #try to find and chmod
        cmd  = 'echo \"find PATH -type f -exec chmod u-wx \"{}\" \\;\"; '
        cmd += 'find $tmp -type f -exec chmod u-wx \"{}\" \\; >/dev/null; err_find=$?; '
        cmd += 'test $err_find -ne 0 && cleanup $err_find; '
        tail.append(cmd)
        #try find suffix '+'
        cmd  = 'find $tmp -type f -exec chmod u-wx \"{}\" + >/dev/null; err_gnu_find=$?; '
        cmd += 'test $err_gnu_find -ne 0 && echo \"gnu_find not supported\"; '
        tail.append(cmd)
        #try to rm -rf
        cmd  = 'echo \"rm -rf PATH\"; rm -rf $tmp >/dev/null; err_rm=$?; '
        cmd += 'test $err_rm -ne 0 && cleanup $err_rm; '
        tail.append(cmd)
        #try nice -n 19
        if self.nice:
            cmd  = 'echo \"nice -n 19\"; nice -n 19 true >/dev/null; err_nice=$?; '
            cmd += 'test $err_nice -ne 0 && cleanup $err_nice; '
            tail.append(cmd)
        #try ionice -c2 -n7
        if self.ionice:
            cmd  = 'echo \"ionice -c2 -n7\"; ionice -c2 -n7 true >/dev/null; err_nice=$?; '
            cmd += 'test $err_nice -ne 0 && cleanup $err_nice; '
            tail.append(cmd)
        #try nocache
        if self.nocache:
            cmd  = 'echo \"nocache\"; nocache true >/dev/null; err_nocache=$?; '
            cmd += 'test $err_nocache -ne 0 && cleanup $err_nocache; '
            tail.append(cmd)
        #try screen, bash and flock used by smart-remove running in background
        if self.config.get_smart_remove_run_remote_in_background(self.profile_id):
            cmd  = 'echo \"screen -d -m bash -c ...\"; screen -d -m bash -c \"true\" >/dev/null; err_screen=$?; '
            cmd += 'test $err_screen -ne 0 && cleanup $err_screen; '
            cmd += 'echo \"(flock -x 9) 9>smr.lock\"; bash -c \"(flock -x 9) 9>smr.lock\" >/dev/null; err_flock=$?; '
            cmd += 'test $err_flock -ne 0 && cleanup $err_flock; '
            tail.append(cmd)
        #if we end up here, everything should be fine
        cmd = 'echo \"done\"'
        tail.append(cmd)

        maxLength = self.config.ssh_max_arg_length(self.profile_id)
        additionalChars = len('echo ""') + len(self.config.ssh_prefix_cmd(self.profile_id, cmd_type = str))

        ssh = ['ssh']
        ssh.extend(self.ssh_options + [self.user_host])
        ssh.extend(self.config.ssh_prefix_cmd(self.profile_id, cmd_type = list))
        output = ''
        err = ''
        returncode = 0
        for cmd in tools.splitCommands(tail,
                                       head = head,
                                       maxLength = maxLength,
                                       additionalChars = additionalChars):
            if cmd.endswith('; '):
                cmd += 'echo ""'
            c = ssh[:]
            c.extend([cmd])
            try:
                logger.debug('Call command: %s' %' '.join(c), self)
                proc = subprocess.Popen(c,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        universal_newlines = True)
                ret = proc.communicate()
            except OSError as e:
                #Argument list too long
                if e.errno == 7:
                    logger.debug('Argument list too log (Python exception)', self)
                    return maxArg()
                else:
                    raise
            logger.debug('Command stdout: %s' %ret[0], self)
            logger.debug('Command stderr: %s' %ret[1], self)
            logger.debug('Command returncode: %s' %proc.returncode, self)
            output += ret[0].strip('\n') + '\n'
            err    += ret[1].strip('\n') + '\n'
            returncode += proc.returncode
            if proc.returncode:
                break

        output_split = output.strip('\n').split('\n')

        while True:
            if output_split and not output_split[-1]:
                output_split = output_split[:-1]
            else:
                break

        if not output_split:
            return maxArg()

        gnu_find_suffix_support = True
        for line in output_split:
            if line.startswith('gnu_find not supported'):
                gnu_find_suffix_support = False
        self.config.set_gnu_find_suffix_support(gnu_find_suffix_support, self.profile_id)

        if returncode or not output_split[-1].startswith('done'):
            for command in ('cp', 'chmod', 'find', 'rm', 'nice', 'ionice', 'nocache', 'screen', '(flock'):
                if output_split[-1].startswith(command):
                    raise MountException( _('Remote host %(host)s doesn\'t support \'%(command)s\':\n'
                                            '%(err)s\nLook at \'man backintime\' for further instructions')
                                            % {'host' : self.host, 'command' : output_split[-1], 'err' : err})
            raise MountException( _('Check commands on host %(host)s returned unknown error:\n'
                                    '%(err)s\nLook at \'man backintime\' for further instructions')
                                    % {'host' : self.host, 'err' : err})

        i = 1
        inode1 = 'ABC'
        inode2 = 'DEF'
        for line in output_split:
            if line.startswith('cp'):
                try:
                    inode1 = output_split[i].split(' ')[0]
                    inode2 = output_split[i+1].split(' ')[0]
                except IndexError:
                    pass
                if not inode1 == inode2:
                    raise MountException( _('Remote host %s doesn\'t support hardlinks') % self.host)
            i += 1