diff --git a/README.md b/README.md index 5a1050a..d04d19b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ It's possible to use a remote server as destination for the backup. Just use the For this to work, rsync must be installed on the server too. ### Server authentication -Right now only authentication using SSH key works. The best way to handle the authentication is to have an ssh agent running on your system, otherwise if a passphrase is necessary to unlock the ssh key, it will be necessary to enter it more than once. +The best way to handle the authentication is to have an ssh agent running on your system, otherwise if a passphrase is necessary to unlock the ssh key, it will be necessary to enter it more than once. If needed, it's possible to specify the ssh key location with the --keyfile argument or in the configuration file. To be able to connect to the user's ssh agent when running simple_backup with sudo, make sure to preserve the SSH_AUTH_SOCK environment variable. For example: @@ -65,3 +65,4 @@ sudo --preserve-env=SSH_AUTH_SOCK -s simple_backup [options] ``` or by editing the sudoers file. +If SSH key authentication is not available, password authentication will be used instead. diff --git a/man/simple_backup.1 b/man/simple_backup.1 index 98b7c61..79b09fc 100644 --- a/man/simple_backup.1 +++ b/man/simple_backup.1 @@ -28,11 +28,13 @@ simple_backup \- Backup files and folders using rsync .RE .SH DESCRIPTION .BR simple_backup -is a python script for performing backup of files and folders. It uses rsync to copy the files -to the specified location. Parameters for the backup such as input files/directories, output -location and files or folders to exclude can be specified +is a python script for performing backup of files and folders. +.P +It uses rsync to copy the files to the specified location. Parameters for the backup such as +input files/directories, output location and files or folders to exclude can be specified in a configuration file (default location $HOME/.config/simple_backup/simple_backup.conf) or directly on the command line. +.P Parameters specified on the command line will override those in the configuration file. .SH OPTIONS .TP @@ -61,7 +63,7 @@ Specify how many old backups (so excluding the current one) will be kept. The de is to keep them all (same as N=\-1) .TP .B \-\-host HOSTNAME -Hostname of the server where to copy the files in case of remote backup through ssh +Hostname of the server where to copy the files in case of remote backup through SSH .TP .B \-u, \-\-username USERNAME Username for connecting to the server in case of remote backup @@ -88,10 +90,38 @@ Copy it to the default location ($HOME/.config/simple_backup) and edit it as nee It is possible to choose a directory on a remote server as destination for the backup. The files are copied by rsync through SSH. Server hostname and username must be specified, either in the configuration file, or on the command line (\(aq\-\-host\(aq and \(aq\-\-username\(aq options). -Currently only authentication with SSH key has been tested. The easiest way to connect to the -server is to use an ssh agent. Otherwise, if the SSH key is encrypted, it will be necessary -to enter the passphrase more than once. It is possible to specify the SSH key to use with the -option \(aq\-\-keyfile\(aq, if necessary. +.SS AUTHENTICATION +For authentication, it is possible to use SSH key or password. +.P +When using SSH key, the best way to connect to the server is to have an SSH agent running. +Otherwise, if the SSH key is encrypted, it will be necessary to enter the passphrase more +than once. It is possible to specify the SSH key to use with the option \(aq\-\-keyfile\(aq, +if necessary. +.P +When running +.B simple_backup +with +.B sudo, +in order to connect to the user\(aq s SSH agent it is necessary to preserve the \(aq SSH_AUTH_SOCK\(aq environment variable, for example: +.P +.EX + sudo --preserve-env=SSH_AUTH_SOCK -s simple_backup [options] +.EE +.P +It is also possible to make this permanent by editing the +.B sudoers +file (see +.B sudoers (5) +) +.P +If SSH key authentication is not available, password authentication will be used instead. +Note that in this case +.B sshpass +(if available) will be used to send the password to rsync, to avoid prompting the user for +the password multiple +times. This can pose some security risks, see +.B sshpass (1) +for details. For this reason, use SSH key authentication if possible. .SH SEE ALSO .BR rsync (1) .SH AUTHORS diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index e63ed72..31c674c 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -10,13 +10,12 @@ Classes: MyFormatter Backup """ - # Import libraries import sys import os import warnings from functools import wraps -from shutil import rmtree +from shutil import rmtree, which import shlex import argparse import configparser @@ -155,6 +154,8 @@ class Backup: self._remote = None self._err_flag = False self._ssh = None + self._password_auth = False + self._password = None def check_params(self): """Check if parameters for the backup are valid""" @@ -325,13 +326,22 @@ class Backup: except paramiko.SSHException: pass - pkey = None - password = None - if self.ssh_keyfile is None: - logger.critical('Can\'t connect to the server. No authentication method available') + try: + password = getpass(f'{self.username}@{self.host}\'s password: ') + ssh.connect(self.host, username=self.username, password=password) - return None + self._password_auth = True + os.environ['SSHPASS'] = password + + return ssh + except paramiko.SSHException as e: + logger.critical('Can\'t connect to the server.') + logger.critical(e) + + return None + + pkey = None try: pkey = RSAKey.from_private_key_file(self.ssh_keyfile) @@ -427,12 +437,16 @@ class Backup: if euid == 0 and self.ssh_keyfile is not None: rsync = f'{rsync} -e \'ssh -i {self.ssh_keyfile}\'' + elif self._password_auth and which('sshpass'): + rsync = f'{rsync} -e \'sshpass -e ssh -l {self.username}\'' args = shlex.split(rsync) with Popen(args, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=False) as p: output, _ = p.communicate() + del os.environ['SSHPASS'] + if p.returncode != 0: self._err_flag = True @@ -444,7 +458,7 @@ class Backup: logger.info('rsync: %s', output[-3]) logger.info('rsync: %s', output[-2]) - if self._remote: + if self._remote and not self._err_flag: _, stdout, _ = \ self._ssh.exec_command(f'if [ -L "{self.output}/simple_backup/last_backup" ]; then echo "ok"; fi') @@ -458,7 +472,7 @@ class Backup: if err != '': logger.error(err) self._err_flag = True - else: + elif not self._err_flag: if os.path.islink(f'{self.output}/simple_backup/last_backup'): try: os.remove(f'{self.output}/simple_backup/last_backup') @@ -478,7 +492,7 @@ class Backup: if err != '': logger.error(err) self._err_flag = True - elif not self._remote: + elif not self._err_flag: try: os.symlink(self._output_dir, f'{self.output}/simple_backup/last_backup', target_is_directory=True) except FileExistsError: