--- keyring/backends/file.py | 85 ++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 44 deletions(-) --- a/keyring/backends/file.py +++ b/keyring/backends/file.py @@ -18,6 +18,7 @@ from ..backend import KeyringBackend from ..util import platform_, properties from ..util.escape import escape as escape_for_ini from oslo_concurrency import lockutils +from tempfile import mkstemp lockfile = "keyringlock" @@ -102,11 +103,9 @@ class BaseKeyring(FileBacked, KeyringBac # encode with base64 password_base64 = base64.encodestring(password_encrypted).decode() - lockdir = os.path.dirname(self.file_path) - - with lockutils.lock(lockfile,external=True,lock_path=lockdir): - + keyringdir = os.path.dirname(self.file_path) + with lockutils.lock(lockfile, external=True, lock_path=keyringdir): config = None try: # Load the keyring from the disk @@ -121,16 +120,20 @@ class BaseKeyring(FileBacked, KeyringBac config.add_section(service) config.set(service, username, password_base64) - # Save the keyring back to the file - storage_root = os.path.dirname(self.file_path) - tmpfile = "tmpfile.%s" % os.getpid() - with open(storage_root + "/" + tmpfile, 'w') as config_file: - config.write(config_file) - # copy will overwrite but move will not - shutil.copy(storage_root + "/" + tmpfile,self.file_path) - # wipe out tmpfile here - os.remove(storage_root + "/" + tmpfile) + # remove any residual temporary files here + try: + for tmpfile in glob.glob("%s/tmp*" % keyringdir): + os.remove(tmpfile) + except: + logging.warning("_check_file: tmpfile removal failed") + # Write the keyring to a temp file, then move the new file + # to avoid overwriting the existing inode + (fd, fname) = mkstemp(dir=keyringdir) + with os.fdopen(fd, "w") as config_file: + config.write(config_file) + os.chmod(fname, os.stat(self.file_path).st_mode) + shutil.move(fname, self.file_path) def _ensure_file_path(self): @@ -167,8 +170,8 @@ class BaseKeyring(FileBacked, KeyringBac service = escape_for_ini(service) username = escape_for_ini(username) - lockdir = os.path.dirname(self.file_path) - with lockutils.lock(lockfile,external=True,lock_path=lockdir): + keyringdir = os.path.dirname(self.file_path) + with lockutils.lock(lockfile, external=True, lock_path=keyringdir): config = configparser.RawConfigParser() if os.path.exists(self.file_path): config.read(self.file_path) @@ -177,15 +180,21 @@ class BaseKeyring(FileBacked, KeyringBac raise PasswordDeleteError("Password not found") except configparser.NoSectionError: raise PasswordDeleteError("Password not found") - # update the file - storage_root = os.path.dirname(self.file_path) - tmpfile = "tmpfile.%s" % os.getpid() - with open(storage_root + "/" + tmpfile, 'w') as config_file: + + # remove any residual temporary files here + try: + for tmpfile in glob.glob("%s/tmp*" % keyringdir): + os.remove(tmpfile) + except: + logging.warning("_check_file: tmpfile removal failed") + + # Write the keyring to a temp file, then move the new file + # to avoid overwriting the existing inode + (fd, fname) = mkstemp(dir=keyringdir) + with os.fdopen(fd, "w") as config_file: config.write(config_file) - # copy will overwrite but move will not - shutil.copy(storage_root + "/" + tmpfile,self.file_path) - # wipe out tmpfile - os.remove(storage_root + "/" + tmpfile) + os.chmod(fname, os.stat(self.file_path).st_mode) + shutil.move(fname, self.file_path) class PlaintextKeyring(BaseKeyring): @@ -294,27 +303,15 @@ class EncryptedKeyring(Encrypted, BaseKe return False self._migrate() - lockdir = os.path.dirname(self.file_path) - # lock access to the file_path here, make sure it's not being written - # to while while we're checking for keyring-setting - with lockutils.lock(lockfile,external=True,lock_path=lockdir): - config = configparser.RawConfigParser() - config.read(self.file_path) - try: - config.get( - escape_for_ini('keyring-setting'), - escape_for_ini('password reference'), - ) - except (configparser.NoSectionError, configparser.NoOptionError): - return False - - # remove any residual temporary files here - try: - for tmpfile in glob.glob(os.path.dirname(self.file_path) + "/" + "tmpfile.*"): - os.remove(tmpfile) - except: - logging.warning("_check_file: tmpfile removal failed") - + config = configparser.RawConfigParser() + config.read(self.file_path) + try: + config.get( + escape_for_ini('keyring-setting'), + escape_for_ini('password reference'), + ) + except (configparser.NoSectionError, configparser.NoOptionError): + return False return True