1 Index: keyring-5.3/keyring/backends/file.py
2 ===================================================================
3 --- keyring-5.3.orig/keyring/backends/file.py
4 +++ keyring-5.3/keyring/backends/file.py
5 @@ -7,6 +7,8 @@ import sys
12 from ..py27compat import configparser
14 @@ -14,6 +16,7 @@ from ..errors import PasswordDeleteError
15 from ..backend import KeyringBackend
16 from ..util import platform_, properties
17 from ..util.escape import escape as escape_for_ini
18 +from oslo_concurrency import lockutils
21 class FileBacked(object):
22 @@ -31,6 +34,13 @@ class FileBacked(object):
24 return os.path.join(platform_.data_root(), self.filename)
26 + @properties.NonDataProperty
27 + def backup_file_path(self):
29 + The path to the file where passwords are stored. This property
30 + may be overridden by the subclass or at the instance level.
32 + return os.path.join(platform_.data_root(), self.backup_filename)
34 class BaseKeyring(FileBacked, KeyringBackend):
36 @@ -78,6 +88,16 @@ class BaseKeyring(FileBacked, KeyringBac
41 + def filecopy(self,src,dest):
42 + """copy file src to dest with default buffer size
44 + with open(src, 'r') as f1:
45 + with open(dest, 'w') as f2:
46 + shutil.copyfileobj(f1,f2)
50 def set_password(self, service, username, password):
51 """Write the password in the file.
53 @@ -89,37 +109,56 @@ class BaseKeyring(FileBacked, KeyringBac
55 password_base64 = base64.encodestring(password_encrypted).decode()
57 - # ensure the file exists
58 - self._ensure_file_path()
60 - # obtain lock for the keyring file
64 - if not os.path.isfile('/tmp/.keyringlock'):
65 - lock = open('/tmp/.keyringlock', 'w')
70 + with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
72 + # ensure the file exists
73 + self._ensure_file_path()
77 + # Load the keyring from the disk
78 + config = configparser.RawConfigParser()
79 + config.read(self.file_path)
80 + except configparser.ParsingError as e:
81 + logging.warning("set_password: keyring file corrupted, Reverting to Backup")
82 + # Revert to the backup file (copy backup over current file)
84 + src = self.backup_file_path
85 + dest = self.file_path
86 + self.filecopy(src,dest)
87 + except shutil.Error as e:
88 + logging.warning("set_password: Revert from Backup failed. Error: %s" % e)
90 + # Load the keyring from the disk, if this fails exception is raised
92 + config = configparser.RawConfigParser()
93 + config.read(self.file_path)
95 + e = sys.exc_info()[0]
96 + logging.warning("set_password: Both keyring files are non useable. Error: %s" % e)
100 - # Load the keyring from the disk
101 - config = configparser.RawConfigParser()
102 - config.read(self.file_path)
104 # Update the keyring with the password
105 if not config.has_section(service):
106 config.add_section(service)
107 config.set(service, username, password_base64)
109 + # Make a back up of the keyring file here
111 + src = self.file_path
112 + dest = self.backup_file_path
113 + self.filecopy(src,dest)
114 + except shutil.Error as e:
115 + logging.warning("set_password: Backup failed. Error: %s" % e)
117 # Save the keyring back to the file
118 with open(self.file_path, 'w') as config_file:
119 config.write(config_file)
122 - os.remove('/tmp/.keyringlock')
127 def _ensure_file_path(self):
128 @@ -142,17 +181,18 @@ class BaseKeyring(FileBacked, KeyringBac
130 service = escape_for_ini(service)
131 username = escape_for_ini(username)
132 - config = configparser.RawConfigParser()
133 - if os.path.exists(self.file_path):
134 - config.read(self.file_path)
136 - if not config.remove_option(service, username):
137 + with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
138 + config = configparser.RawConfigParser()
139 + if os.path.exists(self.file_path):
140 + config.read(self.file_path)
142 + if not config.remove_option(service, username):
143 + raise PasswordDeleteError("Password not found")
144 + except configparser.NoSectionError:
145 raise PasswordDeleteError("Password not found")
146 - except configparser.NoSectionError:
147 - raise PasswordDeleteError("Password not found")
149 - with open(self.file_path, 'w') as config_file:
150 - config.write(config_file)
152 + with open(self.file_path, 'w') as config_file:
153 + config.write(config_file)
155 class PlaintextKeyring(BaseKeyring):
156 """Simple File Keyring with no encryption"""
157 @@ -161,6 +201,7 @@ class PlaintextKeyring(BaseKeyring):
158 "Applicable for all platforms, but not recommended"
160 filename = 'keyring_pass.cfg'
161 + backup_filename = 'crypted_pass_backup.cfg'
163 def encrypt(self, password):
164 """Directly return the password itself.
165 @@ -214,6 +255,7 @@ class EncryptedKeyring(Encrypted, BaseKe
166 """PyCrypto File Keyring"""
168 filename = 'crypted_pass.cfg'
169 + backup_filename = 'crypted_pass_backup.cfg'
170 pw_prefix = 'pw:'.encode()
172 @properties.ClassProperty
173 @@ -247,6 +289,19 @@ class EncryptedKeyring(Encrypted, BaseKe
174 self.keyring_key = self._get_new_password()
175 # set a reference password, used to check that the password provided
176 # matches for subsequent checks.
178 + # try to pre-create the /tmp/keyringlock if it doesn't exist
179 + lockfile = "/tmp/keyringlock"
180 + if os.geteuid() == 0 and (not os.path.exists(lockfile)):
181 + from pwd import getpwnam
183 + nonrootuser = "sysadmin"
184 + with open(lockfile, 'w'):
186 + # must have the lock file with the correct group permissisions g+rw
187 + os.chmod(lockfile, stat.S_IRWXG | stat.S_IRWXU)
190 self.set_password('keyring-setting', 'password reference',
191 'password reference value')
193 @@ -257,15 +312,41 @@ class EncryptedKeyring(Encrypted, BaseKe
194 if not os.path.exists(self.file_path):
197 - config = configparser.RawConfigParser()
198 - config.read(self.file_path)
201 - escape_for_ini('keyring-setting'),
202 - escape_for_ini('password reference'),
204 - except (configparser.NoSectionError, configparser.NoOptionError):
207 + # lock access to the file_path here, make sure it's not being written
208 + # to while while we're checking for keyring-setting
209 + with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
210 + config = configparser.RawConfigParser()
211 + config.read(self.file_path)
214 + escape_for_ini('keyring-setting'),
215 + escape_for_ini('password reference'),
217 + except (configparser.NoSectionError, configparser.NoOptionError):
218 + # The current file doesn't have the keyring-setting, check the backup
219 + logging.warning("_check_file: The current file doesn't have the keyring-setting, check the backup")
220 + if os.path.exists(self.backup_file_path):
221 + config = configparser.RawConfigParser()
222 + config.read(self.backup_file_path)
225 + escape_for_ini('keyring-setting'),
226 + escape_for_ini('password reference'),
228 + except (configparser.NoSectionError, configparser.NoOptionError):
230 + # backup file has it, let's use it
232 + src = self.backup_file_path
233 + dest = self.file_path
234 + shutil.copy(src,dest)
235 + except shutil.Error as e:
236 + logging.warning("Revert from Backup failed. Error: %s" % e)