Revert "Revert "oran-shell-release: release image for F""
[pti/rtp.git] / meta-starlingx / meta-stx-cloud / recipes-devtools / python / files / python-keyring / use_new_lock.patch
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
6  import json
7  import abc
8  import time
9 +import logging
10 +import shutil
11
12  from ..py27compat import configparser
13
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
19
20
21  class FileBacked(object):
22 @@ -31,6 +34,13 @@ class FileBacked(object):
23          """
24          return os.path.join(platform_.data_root(), self.filename)
25
26 +    @properties.NonDataProperty
27 +    def backup_file_path(self):
28 +        """
29 +        The path to the file where passwords are stored. This property
30 +        may be overridden by the subclass or at the instance level.
31 +        """
32 +        return os.path.join(platform_.data_root(), self.backup_filename)
33
34  class BaseKeyring(FileBacked, KeyringBackend):
35      """
36 @@ -78,6 +88,16 @@ class BaseKeyring(FileBacked, KeyringBac
37              password = None
38          return password
39
40 +
41 +    def filecopy(self,src,dest):
42 +        """copy file src to dest with default buffer size
43 +        """
44 +        with open(src, 'r') as f1:
45 +            with open(dest, 'w') as f2:
46 +                shutil.copyfileobj(f1,f2)
47 +                f2.flush()
48 +
49 +
50      def set_password(self, service, username, password):
51          """Write the password in the file.
52          """
53 @@ -89,37 +109,56 @@ class BaseKeyring(FileBacked, KeyringBac
54          # encode with base64
55          password_base64 = base64.encodestring(password_encrypted).decode()
56
57 -        # ensure the file exists
58 -        self._ensure_file_path()
59
60 -        # obtain lock for the keyring file
61 -        lock = ''
62 -        i = 60
63 -        while i:
64 -            if not os.path.isfile('/tmp/.keyringlock'):
65 -                lock = open('/tmp/.keyringlock', 'w')
66 -                break
67 -            else:
68 -                time.sleep(0.500)
69 -                i=i-1
70 +        with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
71
72 +            # ensure the file exists
73 +            self._ensure_file_path()
74 +
75 +            config = None
76 +            try:
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)
83 +                try:
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)
89 +                    raise
90 +                # Load the keyring from the disk, if this fails exception is raised
91 +                try:
92 +                    config = configparser.RawConfigParser()
93 +                    config.read(self.file_path)
94 +                except:
95 +                    e = sys.exc_info()[0]
96 +                    logging.warning("set_password: Both keyring files are non useable. Error: %s" % e)
97 +                    raise
98
99 -        if i:
100 -            # Load the keyring from the disk
101 -            config = configparser.RawConfigParser()
102 -            config.read(self.file_path)
103
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)
108
109 +            # Make a back up of the keyring file here
110 +            try:
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)
116 +
117              # Save the keyring back to the file
118              with open(self.file_path, 'w') as config_file:
119                  config.write(config_file)
120
121 -            lock.close()
122 -            os.remove('/tmp/.keyringlock')
123 +
124 +
125
126
127      def _ensure_file_path(self):
128 @@ -142,17 +181,18 @@ class BaseKeyring(FileBacked, KeyringBac
129          """
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)
135 -        try:
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)
141 +            try:
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")
148 -        # update the file
149 -        with open(self.file_path, 'w') as config_file:
150 -            config.write(config_file)
151 +            # update the file
152 +            with open(self.file_path, 'w') as config_file:
153 +                config.write(config_file)
154
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"
159
160      filename = 'keyring_pass.cfg'
161 +    backup_filename = 'crypted_pass_backup.cfg'
162
163      def encrypt(self, password):
164          """Directly return the password itself.
165 @@ -214,6 +255,7 @@ class EncryptedKeyring(Encrypted, BaseKe
166      """PyCrypto File Keyring"""
167
168      filename = 'crypted_pass.cfg'
169 +    backup_filename = 'crypted_pass_backup.cfg'
170      pw_prefix = 'pw:'.encode()
171
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.
177 +
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
182 +             import stat
183 +             nonrootuser = "sysadmin"
184 +             with open(lockfile, 'w'):
185 +                 pass
186 +             # must have the lock file with the correct group permissisions g+rw
187 +             os.chmod(lockfile, stat.S_IRWXG | stat.S_IRWXU)
188 +
189 +
190          self.set_password('keyring-setting', 'password reference',
191              'password reference value')
192
193 @@ -257,15 +312,41 @@ class EncryptedKeyring(Encrypted, BaseKe
194          if not os.path.exists(self.file_path):
195              return False
196          self._migrate()
197 -        config = configparser.RawConfigParser()
198 -        config.read(self.file_path)
199 -        try:
200 -            config.get(
201 -                escape_for_ini('keyring-setting'),
202 -                escape_for_ini('password reference'),
203 -            )
204 -        except (configparser.NoSectionError, configparser.NoOptionError):
205 -            return False
206 +
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)
212 +            try:
213 +                config.get(
214 +                    escape_for_ini('keyring-setting'),
215 +                    escape_for_ini('password reference'),
216 +                )
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)
223 +                    try:
224 +                        config.get(
225 +                            escape_for_ini('keyring-setting'),
226 +                            escape_for_ini('password reference'),
227 +                        )
228 +                    except (configparser.NoSectionError, configparser.NoOptionError):
229 +                        return False
230 +                    # backup file has it, let's use it
231 +                    try:
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)
237 +                        return False
238 +                else:
239 +                    return False
240 +
241          return True
242
243      def _unlock(self):