diff --git a/debian/control b/debian/control index 34e1d8928..318d6d8b4 100644 --- a/debian/control +++ b/debian/control @@ -38,7 +38,6 @@ Build-Depends: python3-markupsafe, python3-mypy, python3-pampy, - python3-paramiko, python3-pexpect, python3-pip, python3-psutil, @@ -114,7 +113,6 @@ Depends: python3-gi, python3-markupsafe, python3-pampy, - python3-paramiko, python3-pexpect, python3-psutil, python3-requests, diff --git a/doc/dev/conf.py b/doc/dev/conf.py index 7fee95aa9..dada84f0f 100644 --- a/doc/dev/conf.py +++ b/doc/dev/conf.py @@ -211,7 +211,6 @@ autodoc_mock_imports = [ 'gi', 'markupsafe', 'pam', - 'paramiko', 'psutil', 'pytest', 'requests', diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index 5a6c6db19..7e880102e 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -6,8 +6,8 @@ import logging import os import pathlib import re +import subprocess -import paramiko from django.utils.text import get_valid_filename from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_noop @@ -51,7 +51,8 @@ class BackupsApp(app_module.App): order=20) self.add(menu_item) - packages = Packages('packages-backups', ['borgbackup', 'sshfs']) + packages = Packages('packages-backups', + ['borgbackup', 'sshfs', 'sshpass']) self.add(packages) @staticmethod @@ -143,9 +144,13 @@ def is_ssh_hostkey_verified(hostname): if not known_hosts_path.exists(): return False - known_hosts = paramiko.hostkeys.HostKeys(str(known_hosts_path)) - host_keys = known_hosts.lookup(hostname) - return host_keys is not None + try: + subprocess.run( + ['ssh-keygen', '-F', hostname, '-f', + str(known_hosts_path)], check=True) + return True + except subprocess.CalledProcessError: + return False def split_path(path): diff --git a/plinth/modules/backups/repository.py b/plinth/modules/backups/repository.py index 614486316..a135ff495 100644 --- a/plinth/modules/backups/repository.py +++ b/plinth/modules/backups/repository.py @@ -7,9 +7,9 @@ import io import logging import os import re +import subprocess from uuid import uuid1 -import paramiko from django.utils.translation import gettext_lazy as _ from plinth import cfg @@ -493,28 +493,13 @@ class SshBorgRepository(BaseBorgRepository): password = self.credentials['ssh_password'] # Ensure remote directory exists, check contents - # TODO Test with IPv6 connection - with _ssh_connection(hostname, username, password) as ssh_client: - with ssh_client.open_sftp() as sftp_client: - try: - sftp_client.listdir(dir_path) - except FileNotFoundError: - logger.info('Directory %s does not exist, creating.', - dir_path) - sftp_client.mkdir(dir_path) - - -@contextlib.contextmanager -def _ssh_connection(hostname, username, password): - """Context manager to create and close an SSH connection.""" - ssh_client = paramiko.SSHClient() - ssh_client.load_host_keys(str(get_known_hosts_path())) - - try: - ssh_client.connect(hostname, username=username, password=password) - yield ssh_client - finally: - ssh_client.close() + env = {'SSHPASS': password} + known_hosts_path = str(get_known_hosts_path()) + subprocess.run([ + 'sshpass', '-e', 'ssh', '-o', + f'UserKnownHostsFile={known_hosts_path}', f'{username}@{hostname}', + 'mkdir', '-p', dir_path + ], check=True, env=env) def get_repositories(): diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py index ba4780ffb..134f80860 100644 --- a/plinth/modules/backups/views.py +++ b/plinth/modules/backups/views.py @@ -5,10 +5,10 @@ Views for the backups app. import logging import os +import subprocess from datetime import datetime from urllib.parse import unquote -import paramiko from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.http import Http404, StreamingHttpResponse @@ -427,13 +427,14 @@ def _save_repository(request, repository): repository.verified = True repository.save() return True - except paramiko.BadHostKeyException: - message = _('SSH host public key could not be verified.') - except paramiko.AuthenticationException: - message = _('Authentication to remote server failed.') - except paramiko.SSHException as exception: - message = _('Error establishing connection to server: {}').format( - str(exception)) + except subprocess.CalledProcessError as exception: + if exception.returncode in (6, 7): + message = _('SSH host public key could not be verified.') + elif exception.returncode == 5: + message = _('Authentication to remote server failed.') + else: + message = _('Error establishing connection to server: {}').format( + str(exception)) except Exception as exception: message = str(exception) logger.exception('Error adding repository: %s', exception)