Relocate python-keyring to stx-integ/security/python-keyring
Move content from stx-gplv3 into stx-integ Packages will be relocated to stx-integ: base/ anaconda crontabs dnsmasq rsync database/ python-psycopg2 filesystem/ parted grub/ grub2 security/ python-keyring Change-Id: I17163dbff41222985a29228a8b42c919a86d1e67 Story: 2002801 Task: 22687 Signed-off-by: Scott Little <scott.little@windriver.com>
This commit is contained in:
parent
02c9baa147
commit
9f0e32eab4
@ -144,3 +144,4 @@ base/crontabs
|
||||
base/dnsmasq
|
||||
base/rsync
|
||||
filesystem/parted
|
||||
security/python-keyring
|
||||
|
16
security/python-keyring/PKG-INFO
Normal file
16
security/python-keyring/PKG-INFO
Normal file
@ -0,0 +1,16 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: python-keyring
|
||||
Version: 5.7
|
||||
Summary: Python 2 library to store and access passwords safely
|
||||
Home-page: https://github.com/jaraco/keyring
|
||||
Author:
|
||||
Author-email:
|
||||
License: MIT and Python
|
||||
|
||||
Description:
|
||||
The Python keyring lib provides a easy way to access the system keyring
|
||||
service from python. It can be used in any application that needs safe
|
||||
password storage.
|
||||
|
||||
|
||||
Platform: UNKNOWN
|
2
security/python-keyring/centos/build_srpm.data
Normal file
2
security/python-keyring/centos/build_srpm.data
Normal file
@ -0,0 +1,2 @@
|
||||
COPY_LIST="python-keyring/*"
|
||||
TIS_PATCH_VER=2
|
@ -0,0 +1,78 @@
|
||||
From d7f5646de9ec990ed81489cc12d7942654bc017d Mon Sep 17 00:00:00 2001
|
||||
From: Kam Nasim <kam.nasim@windriver.com>
|
||||
Date: Fri, 23 Dec 2016 14:30:17 -0500
|
||||
Subject: [PATCH] first meta patch to move python-keyring package from download
|
||||
tarball to srpm. Also updated to add tis patch versioning
|
||||
|
||||
---
|
||||
SPECS/python-keyring.spec | 30 +++++++++++++++++++++++++++---
|
||||
1 file changed, 27 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/SPECS/python-keyring.spec b/SPECS/python-keyring.spec
|
||||
index 14e4e93..60d05ee 100644
|
||||
--- a/SPECS/python-keyring.spec
|
||||
+++ b/SPECS/python-keyring.spec
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Name: python-keyring
|
||||
Version: 5.7.1
|
||||
-Release: 1%{?dist}
|
||||
+Release: 1%{?_tis_dist}.%{tis_patch_ver}
|
||||
Summary: Python 2 library to store and access passwords safely
|
||||
License: MIT and Python
|
||||
URL: http://bitbucket.org/kang/python-keyring-lib/
|
||||
@@ -10,10 +10,21 @@ Source0: https://pypi.io/packages/source/k/keyring/keyring-%{version}.tar
|
||||
BuildArch: noarch
|
||||
BuildRequires: python2-devel
|
||||
BuildRequires: python-setuptools
|
||||
-BuildRequires: python-setuptools_scm
|
||||
Obsoletes: %{name}-kwallet < %{version}-%{release}
|
||||
Obsoletes: %{name}-gnome < %{version}-%{release}
|
||||
|
||||
+Patch0: no_keyring_password.patch
|
||||
+Patch1: lock_keyring_file.patch
|
||||
+Patch2: lock_keyring_file2.patch
|
||||
+Patch3: use_new_lock.patch
|
||||
+Patch4: fix_keyring_lockfile_location.patch
|
||||
+Patch5: use_temporary_file.patch
|
||||
+Patch6: chown_keyringlock_file.patch
|
||||
+Patch7: chmod_keyringlock2.patch
|
||||
+Patch8: keyring_path_change.patch
|
||||
+Patch9: remove-reader-lock.patch
|
||||
+Patch10: remove_others_perms_on_keyringcfg_file.patch
|
||||
+
|
||||
%description
|
||||
The Python keyring lib provides a easy way to access the system keyring
|
||||
service from python. It can be used in any application that needs safe
|
||||
@@ -39,7 +50,6 @@ Python keyring lib also provides following build-in keyrings.
|
||||
Summary: Python 3 library to access the system keyring service
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-setuptools
|
||||
-BuildRequires: python3-setuptools_scm
|
||||
|
||||
%description -n python3-keyring
|
||||
The Python keyring lib provides a easy way to access the system keyring
|
||||
@@ -64,6 +74,20 @@ Python keyring lib also provides following build-in keyrings.
|
||||
|
||||
%prep
|
||||
%setup -qn keyring-%{version}
|
||||
+
|
||||
+# WRS
|
||||
+%patch0 -p1
|
||||
+%patch1 -p1
|
||||
+%patch2 -p1
|
||||
+%patch3 -p1
|
||||
+%patch4 -p1
|
||||
+%patch5 -p1
|
||||
+%patch6 -p1
|
||||
+%patch7 -p1
|
||||
+%patch8 -p1
|
||||
+%patch9 -p1
|
||||
+%patch10 -p1
|
||||
+
|
||||
rm -frv keyring.egg-info
|
||||
# Drop redundant shebangs.
|
||||
sed -i '1{\@^#!/usr/bin/env python@d}' keyring/cli.py
|
||||
--
|
||||
1.8.3.1
|
||||
|
@ -0,0 +1,20 @@
|
||||
diff --git a/SPECS/python-keyring.spec b/SPECS/python-keyring.spec
|
||||
index 60d05ee..a41f849 100644
|
||||
--- a/SPECS/python-keyring.spec
|
||||
+++ b/SPECS/python-keyring.spec
|
||||
@@ -10,6 +10,7 @@ Source0: https://pypi.io/packages/source/k/keyring/keyring-%{version}.tar
|
||||
BuildArch: noarch
|
||||
BuildRequires: python2-devel
|
||||
BuildRequires: python-setuptools
|
||||
+BuildRequires: python2-setuptools_scm
|
||||
Obsoletes: %{name}-kwallet < %{version}-%{release}
|
||||
Obsoletes: %{name}-gnome < %{version}-%{release}
|
||||
|
||||
@@ -50,6 +51,7 @@ Python keyring lib also provides following build-in keyrings.
|
||||
Summary: Python 3 library to access the system keyring service
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-setuptools
|
||||
+BuildRequires: python3-setuptools_scm
|
||||
|
||||
%description -n python3-keyring
|
||||
The Python keyring lib provides a easy way to access the system keyring
|
2
security/python-keyring/centos/meta_patches/PATCH_ORDER
Normal file
2
security/python-keyring/centos/meta_patches/PATCH_ORDER
Normal file
@ -0,0 +1,2 @@
|
||||
0001-move-package-from-tarball-to-srpm.patch
|
||||
0002-meta-buildrequires-python-setuptools_scm.patch
|
1
security/python-keyring/centos/srpm_path
Normal file
1
security/python-keyring/centos/srpm_path
Normal file
@ -0,0 +1 @@
|
||||
mirror:Source/python-keyring-5.7.1-1.el7.src.rpm
|
@ -0,0 +1,37 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -68,6 +68,9 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
service = escape_for_ini(service)
|
||||
username = escape_for_ini(username)
|
||||
|
||||
+ # ensure the file exists
|
||||
+ self._ensure_file_path()
|
||||
+
|
||||
# load the passwords from the file
|
||||
config = configparser.RawConfigParser()
|
||||
if os.path.exists(self.file_path):
|
||||
@@ -146,12 +149,16 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
user_read_write = 0o644
|
||||
os.chmod(self.file_path, user_read_write)
|
||||
if not os.path.isfile(lockdir + "/" + lockfile):
|
||||
- import stat
|
||||
- with open(lockdir + "/" + lockfile, 'w'):
|
||||
- pass
|
||||
- # must have the lock file with the correct group permissisions g+rw
|
||||
- os.chmod(lockdir + "/" + lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
- os.chown(lockdir + "/" + lockfile,-1,345)
|
||||
+ with open(lockdir + "/" + lockfile, 'w'):
|
||||
+ pass
|
||||
+ if os.path.isfile(lockdir + "/" + lockfile):
|
||||
+ import stat
|
||||
+ import grp
|
||||
+ if oct(stat.S_IMODE(os.stat(lockdir + "/" + lockfile).st_mode)) != '0770':
|
||||
+ # Must have the lock file with the correct group and permissisions g+rw
|
||||
+ os.chmod(lockdir + "/" + lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
+ groupinfo = grp.getgrnam('wrs_protected')
|
||||
+ os.chown(lockdir + "/" + lockfile,-1,groupinfo.gr_gid)
|
||||
|
||||
|
||||
def delete_password(self, service, username):
|
@ -0,0 +1,12 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -151,6 +151,7 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
pass
|
||||
# must have the lock file with the correct group permissisions g+rw
|
||||
os.chmod(lockdir + "/" + lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
+ os.chown(lockdir + "/" + lockfile,-1,345)
|
||||
|
||||
|
||||
def delete_password(self, service, username):
|
@ -0,0 +1,113 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -19,6 +19,8 @@ from ..util.escape import escape as esca
|
||||
from oslo_concurrency import lockutils
|
||||
|
||||
|
||||
+lockfile = "keyringlock"
|
||||
+
|
||||
class FileBacked(object):
|
||||
@abc.abstractproperty
|
||||
def filename(self):
|
||||
@@ -104,16 +106,18 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
service = escape_for_ini(service)
|
||||
username = escape_for_ini(username)
|
||||
|
||||
+ # ensure the file exists
|
||||
+ self._ensure_file_path()
|
||||
+
|
||||
# encrypt the password
|
||||
password_encrypted = self.encrypt(password.encode('utf-8'))
|
||||
# encode with base64
|
||||
password_base64 = base64.encodestring(password_encrypted).decode()
|
||||
|
||||
+ lockdir = os.path.dirname(self.file_path)
|
||||
|
||||
- with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
|
||||
+ with lockutils.lock(lockfile,external=True,lock_path=lockdir):
|
||||
|
||||
- # ensure the file exists
|
||||
- self._ensure_file_path()
|
||||
|
||||
config = None
|
||||
try:
|
||||
@@ -159,14 +163,13 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
|
||||
|
||||
|
||||
-
|
||||
-
|
||||
def _ensure_file_path(self):
|
||||
"""
|
||||
Ensure the storage path exists.
|
||||
If it doesn't, create it with "go-rwx" permissions.
|
||||
"""
|
||||
storage_root = os.path.dirname(self.file_path)
|
||||
+ lockdir = storage_root
|
||||
if storage_root and not os.path.isdir(storage_root):
|
||||
os.makedirs(storage_root)
|
||||
if not os.path.isfile(self.file_path):
|
||||
@@ -175,13 +178,22 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
pass
|
||||
user_read_write = 0o644
|
||||
os.chmod(self.file_path, user_read_write)
|
||||
+ if not os.path.isfile(lockdir + "/" + lockfile):
|
||||
+ import stat
|
||||
+ with open(lockdir + "/" + lockfile, 'w'):
|
||||
+ pass
|
||||
+ # must have the lock file with the correct group permissisions g+rw
|
||||
+ os.chmod(lockdir + "/" + lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
+
|
||||
|
||||
def delete_password(self, service, username):
|
||||
"""Delete the password for the username of the service.
|
||||
"""
|
||||
service = escape_for_ini(service)
|
||||
username = escape_for_ini(username)
|
||||
- with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
|
||||
+
|
||||
+ lockdir = os.path.dirname(self.file_path)
|
||||
+ with lockutils.lock(lockfile,external=True,lock_path=lockdir):
|
||||
config = configparser.RawConfigParser()
|
||||
if os.path.exists(self.file_path):
|
||||
config.read(self.file_path)
|
||||
@@ -290,17 +302,6 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
# set a reference password, used to check that the password provided
|
||||
# matches for subsequent checks.
|
||||
|
||||
- # try to pre-create the /tmp/keyringlock if it doesn't exist
|
||||
- lockfile = "/tmp/keyringlock"
|
||||
- if os.geteuid() == 0 and (not os.path.exists(lockfile)):
|
||||
- from pwd import getpwnam
|
||||
- import stat
|
||||
- nonrootuser = "wrsroot"
|
||||
- with open(lockfile, 'w'):
|
||||
- pass
|
||||
- # must have the lock file with the correct group permissisions g+rw
|
||||
- os.chmod(lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
-
|
||||
|
||||
self.set_password('keyring-setting', 'password reference',
|
||||
'password reference value')
|
||||
@@ -313,9 +314,10 @@ 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("keyringlock",external=True,lock_path="/tmp"):
|
||||
+ with lockutils.lock(lockfile,external=True,lock_path=lockdir):
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(self.file_path)
|
||||
try:
|
||||
@@ -325,7 +327,6 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
# The current file doesn't have the keyring-setting, check the backup
|
||||
- logging.warning("_check_file: The current file doesn't have the keyring-setting, check the backup")
|
||||
if os.path.exists(self.backup_file_path):
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(self.backup_file_path)
|
@ -0,0 +1,24 @@
|
||||
---
|
||||
keyring/util/platform_.py | 4 +++-
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/keyring/util/platform_.py
|
||||
+++ b/keyring/util/platform_.py
|
||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import platform
|
||||
+from tsconfig.tsconfig import SW_VERSION
|
||||
|
||||
def _settings_root_XP():
|
||||
return os.path.join(os.environ['USERPROFILE'], 'Local Settings')
|
||||
@@ -19,7 +20,8 @@ def _data_root_Linux():
|
||||
Use freedesktop.org Base Dir Specfication to determine storage
|
||||
location.
|
||||
"""
|
||||
- fallback = os.path.expanduser('/opt/platform/.keyring/')
|
||||
+ keyring_dir = os.path.join('/opt/platform/.keyring', SW_VERSION)
|
||||
+ fallback = os.path.expanduser(keyring_dir)
|
||||
root = os.environ.get('XDG_DATA_HOME', None) or fallback
|
||||
return os.path.join(root, 'python_keyring')
|
||||
|
@ -0,0 +1,45 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -6,6 +6,7 @@ import base64
|
||||
import sys
|
||||
import json
|
||||
import abc
|
||||
+import time
|
||||
|
||||
from ..py27compat import configparser
|
||||
|
||||
@@ -95,14 +96,29 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(self.file_path)
|
||||
|
||||
+ # obtain lock for the keyring file
|
||||
+ lock = ''
|
||||
+ i = 60
|
||||
+ while i:
|
||||
+ if not os.path.isfile('/tmp/.keyringlock'):
|
||||
+ lock = open('/tmp/.keyringlock', 'w')
|
||||
+ break
|
||||
+ else:
|
||||
+ time.sleep(0.500)
|
||||
+ i=i-1
|
||||
+
|
||||
# update the keyring with the password
|
||||
if not config.has_section(service):
|
||||
config.add_section(service)
|
||||
config.set(service, username, password_base64)
|
||||
|
||||
- # save the keyring back to the file
|
||||
- with open(self.file_path, 'w') as config_file:
|
||||
- config.write(config_file)
|
||||
+ if i:
|
||||
+ # save the keyring back to the file
|
||||
+ with open(self.file_path, 'w') as config_file:
|
||||
+ config.write(config_file)
|
||||
+ lock.close()
|
||||
+ os.remove('/tmp/.keyringlock')
|
||||
+
|
||||
|
||||
def _ensure_file_path(self):
|
||||
"""
|
@ -0,0 +1,42 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -92,10 +92,6 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
# ensure the file exists
|
||||
self._ensure_file_path()
|
||||
|
||||
- # load the keyring from the disk
|
||||
- config = configparser.RawConfigParser()
|
||||
- config.read(self.file_path)
|
||||
-
|
||||
# obtain lock for the keyring file
|
||||
lock = ''
|
||||
i = 60
|
||||
@@ -107,15 +103,21 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
time.sleep(0.500)
|
||||
i=i-1
|
||||
|
||||
- # update the keyring with the password
|
||||
- if not config.has_section(service):
|
||||
- config.add_section(service)
|
||||
- config.set(service, username, password_base64)
|
||||
|
||||
if i:
|
||||
- # save the keyring back to the file
|
||||
+ # Load the keyring from the disk
|
||||
+ config = configparser.RawConfigParser()
|
||||
+ config.read(self.file_path)
|
||||
+
|
||||
+ # Update the keyring with the password
|
||||
+ if not config.has_section(service):
|
||||
+ config.add_section(service)
|
||||
+ config.set(service, username, password_base64)
|
||||
+
|
||||
+ # Save the keyring back to the file
|
||||
with open(self.file_path, 'w') as config_file:
|
||||
config.write(config_file)
|
||||
+
|
||||
lock.close()
|
||||
os.remove('/tmp/.keyringlock')
|
||||
|
@ -0,0 +1,70 @@
|
||||
Index: keyring-3.2/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-3.2.orig/keyring/backends/file.py
|
||||
+++ keyring-3.2/keyring/backends/file.py
|
||||
@@ -114,7 +114,7 @@ class BaseKeyring(KeyringBackend):
|
||||
# create the file without group/world permissions
|
||||
with open(self.file_path, 'w'):
|
||||
pass
|
||||
- user_read_write = 0o600
|
||||
+ user_read_write = 0o644
|
||||
os.chmod(self.file_path, user_read_write)
|
||||
|
||||
def delete_password(self, service, username):
|
||||
@@ -188,12 +188,19 @@ class EncryptedKeyring(BaseKeyring):
|
||||
|
||||
def _get_new_password(self):
|
||||
while True:
|
||||
- password = getpass.getpass(
|
||||
- "Please set a password for your new keyring: ")
|
||||
- confirm = getpass.getpass('Please confirm the password: ')
|
||||
- if password != confirm:
|
||||
- sys.stderr.write("Error: Your passwords didn't match\n")
|
||||
- continue
|
||||
+#****************************************************************
|
||||
+# Forging the Keyring password to allow automation and still keep
|
||||
+# the password encoded. TODO to be revisited when Barbican keyring
|
||||
+# Will be used with the complete PKI solution
|
||||
+#****************************************************************
|
||||
+# password = getpass.getpass(
|
||||
+# "Please set a password for your new keyring: ")
|
||||
+# confirm = getpass.getpass('Please confirm the password: ')
|
||||
+# if password != confirm:
|
||||
+# sys.stderr.write("Error: Your passwords didn't match\n")
|
||||
+# continue
|
||||
+ password = "Please set a password for your new keyring: "
|
||||
+
|
||||
if '' == password.strip():
|
||||
# forbid the blank password
|
||||
sys.stderr.write("Error: blank passwords aren't allowed.\n")
|
||||
@@ -233,8 +240,15 @@ class EncryptedKeyring(BaseKeyring):
|
||||
Unlock this keyring by getting the password for the keyring from the
|
||||
user.
|
||||
"""
|
||||
- self.keyring_key = getpass.getpass(
|
||||
- 'Please enter password for encrypted keyring: ')
|
||||
+#****************************************************************
|
||||
+# Forging the Keyring password to allow automation and still keep
|
||||
+# the password encoded. TODO to be revisited when Barbican keyring
|
||||
+# Will be used with the complete PKI solution
|
||||
+#****************************************************************
|
||||
+# self.keyring_key = getpass.getpass(
|
||||
+# 'Please enter password for encrypted keyring: ')
|
||||
+ self.keyring_key = "Please set a password for your new keyring: "
|
||||
+
|
||||
try:
|
||||
ref_pw = self.get_password('keyring-setting', 'password reference')
|
||||
assert ref_pw == 'password reference value'
|
||||
Index: keyring-3.2/keyring/util/platform_.py
|
||||
===================================================================
|
||||
--- keyring-3.2.orig/keyring/util/platform_.py
|
||||
+++ keyring-3.2/keyring/util/platform_.py
|
||||
@@ -16,7 +16,7 @@ def _data_root_Linux():
|
||||
Use freedesktop.org Base Dir Specfication to determine storage
|
||||
location.
|
||||
"""
|
||||
- fallback = os.path.expanduser('~/.local/share')
|
||||
+ fallback = os.path.expanduser('/opt/platform/.keyring/')
|
||||
root = os.environ.get('XDG_DATA_HOME', None) or fallback
|
||||
return os.path.join(root, 'python_keyring')
|
||||
|
136
security/python-keyring/python-keyring/remove-reader-lock.patch
Normal file
136
security/python-keyring/python-keyring/remove-reader-lock.patch
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
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
|
||||
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
keyring/backends/file.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
--- a/keyring/backends/file.py
|
||||
+++ b/keyring/backends/file.py
|
||||
@@ -149,7 +149,7 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
# create the file without group/world permissions
|
||||
with open(self.file_path, 'w'):
|
||||
pass
|
||||
- user_read_write = 0o644
|
||||
+ user_read_write = 0o640
|
||||
os.chmod(self.file_path, user_read_write)
|
||||
if not os.path.isfile(lockdir + "/" + lockfile):
|
||||
with open(lockdir + "/" + lockfile, 'w'):
|
243
security/python-keyring/python-keyring/use_new_lock.patch
Normal file
243
security/python-keyring/python-keyring/use_new_lock.patch
Normal file
@ -0,0 +1,243 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -7,6 +7,8 @@ import sys
|
||||
import json
|
||||
import abc
|
||||
import time
|
||||
+import logging
|
||||
+import shutil
|
||||
|
||||
from ..py27compat import configparser
|
||||
|
||||
@@ -14,6 +16,7 @@ from ..errors import PasswordDeleteError
|
||||
from ..backend import KeyringBackend
|
||||
from ..util import platform_, properties
|
||||
from ..util.escape import escape as escape_for_ini
|
||||
+from oslo_concurrency import lockutils
|
||||
|
||||
|
||||
class FileBacked(object):
|
||||
@@ -31,6 +34,13 @@ class FileBacked(object):
|
||||
"""
|
||||
return os.path.join(platform_.data_root(), self.filename)
|
||||
|
||||
+ @properties.NonDataProperty
|
||||
+ def backup_file_path(self):
|
||||
+ """
|
||||
+ The path to the file where passwords are stored. This property
|
||||
+ may be overridden by the subclass or at the instance level.
|
||||
+ """
|
||||
+ return os.path.join(platform_.data_root(), self.backup_filename)
|
||||
|
||||
class BaseKeyring(FileBacked, KeyringBackend):
|
||||
"""
|
||||
@@ -78,6 +88,16 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
password = None
|
||||
return password
|
||||
|
||||
+
|
||||
+ def filecopy(self,src,dest):
|
||||
+ """copy file src to dest with default buffer size
|
||||
+ """
|
||||
+ with open(src, 'r') as f1:
|
||||
+ with open(dest, 'w') as f2:
|
||||
+ shutil.copyfileobj(f1,f2)
|
||||
+ f2.flush()
|
||||
+
|
||||
+
|
||||
def set_password(self, service, username, password):
|
||||
"""Write the password in the file.
|
||||
"""
|
||||
@@ -89,37 +109,56 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
# encode with base64
|
||||
password_base64 = base64.encodestring(password_encrypted).decode()
|
||||
|
||||
- # ensure the file exists
|
||||
- self._ensure_file_path()
|
||||
|
||||
- # obtain lock for the keyring file
|
||||
- lock = ''
|
||||
- i = 60
|
||||
- while i:
|
||||
- if not os.path.isfile('/tmp/.keyringlock'):
|
||||
- lock = open('/tmp/.keyringlock', 'w')
|
||||
- break
|
||||
- else:
|
||||
- time.sleep(0.500)
|
||||
- i=i-1
|
||||
+ with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
|
||||
|
||||
+ # ensure the file exists
|
||||
+ self._ensure_file_path()
|
||||
+
|
||||
+ config = None
|
||||
+ try:
|
||||
+ # Load the keyring from the disk
|
||||
+ config = configparser.RawConfigParser()
|
||||
+ config.read(self.file_path)
|
||||
+ except configparser.ParsingError as e:
|
||||
+ logging.warning("set_password: keyring file corrupted, Reverting to Backup")
|
||||
+ # Revert to the backup file (copy backup over current file)
|
||||
+ try:
|
||||
+ src = self.backup_file_path
|
||||
+ dest = self.file_path
|
||||
+ self.filecopy(src,dest)
|
||||
+ except shutil.Error as e:
|
||||
+ logging.warning("set_password: Revert from Backup failed. Error: %s" % e)
|
||||
+ raise
|
||||
+ # Load the keyring from the disk, if this fails exception is raised
|
||||
+ try:
|
||||
+ config = configparser.RawConfigParser()
|
||||
+ config.read(self.file_path)
|
||||
+ except:
|
||||
+ e = sys.exc_info()[0]
|
||||
+ logging.warning("set_password: Both keyring files are non useable. Error: %s" % e)
|
||||
+ raise
|
||||
|
||||
- if i:
|
||||
- # Load the keyring from the disk
|
||||
- config = configparser.RawConfigParser()
|
||||
- config.read(self.file_path)
|
||||
|
||||
# Update the keyring with the password
|
||||
if not config.has_section(service):
|
||||
config.add_section(service)
|
||||
config.set(service, username, password_base64)
|
||||
|
||||
+ # Make a back up of the keyring file here
|
||||
+ try:
|
||||
+ src = self.file_path
|
||||
+ dest = self.backup_file_path
|
||||
+ self.filecopy(src,dest)
|
||||
+ except shutil.Error as e:
|
||||
+ logging.warning("set_password: Backup failed. Error: %s" % e)
|
||||
+
|
||||
# Save the keyring back to the file
|
||||
with open(self.file_path, 'w') as config_file:
|
||||
config.write(config_file)
|
||||
|
||||
- lock.close()
|
||||
- os.remove('/tmp/.keyringlock')
|
||||
+
|
||||
+
|
||||
|
||||
|
||||
def _ensure_file_path(self):
|
||||
@@ -142,17 +181,18 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
"""
|
||||
service = escape_for_ini(service)
|
||||
username = escape_for_ini(username)
|
||||
- config = configparser.RawConfigParser()
|
||||
- if os.path.exists(self.file_path):
|
||||
- config.read(self.file_path)
|
||||
- try:
|
||||
- if not config.remove_option(service, username):
|
||||
+ with lockutils.lock("keyringlock",external=True,lock_path="/tmp"):
|
||||
+ config = configparser.RawConfigParser()
|
||||
+ if os.path.exists(self.file_path):
|
||||
+ config.read(self.file_path)
|
||||
+ try:
|
||||
+ if not config.remove_option(service, username):
|
||||
+ raise PasswordDeleteError("Password not found")
|
||||
+ except configparser.NoSectionError:
|
||||
raise PasswordDeleteError("Password not found")
|
||||
- except configparser.NoSectionError:
|
||||
- raise PasswordDeleteError("Password not found")
|
||||
- # update the file
|
||||
- with open(self.file_path, 'w') as config_file:
|
||||
- config.write(config_file)
|
||||
+ # update the file
|
||||
+ with open(self.file_path, 'w') as config_file:
|
||||
+ config.write(config_file)
|
||||
|
||||
class PlaintextKeyring(BaseKeyring):
|
||||
"""Simple File Keyring with no encryption"""
|
||||
@@ -161,6 +201,7 @@ class PlaintextKeyring(BaseKeyring):
|
||||
"Applicable for all platforms, but not recommended"
|
||||
|
||||
filename = 'keyring_pass.cfg'
|
||||
+ backup_filename = 'crypted_pass_backup.cfg'
|
||||
|
||||
def encrypt(self, password):
|
||||
"""Directly return the password itself.
|
||||
@@ -214,6 +255,7 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
"""PyCrypto File Keyring"""
|
||||
|
||||
filename = 'crypted_pass.cfg'
|
||||
+ backup_filename = 'crypted_pass_backup.cfg'
|
||||
pw_prefix = 'pw:'.encode()
|
||||
|
||||
@properties.ClassProperty
|
||||
@@ -247,6 +289,19 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
self.keyring_key = self._get_new_password()
|
||||
# set a reference password, used to check that the password provided
|
||||
# matches for subsequent checks.
|
||||
+
|
||||
+ # try to pre-create the /tmp/keyringlock if it doesn't exist
|
||||
+ lockfile = "/tmp/keyringlock"
|
||||
+ if os.geteuid() == 0 and (not os.path.exists(lockfile)):
|
||||
+ from pwd import getpwnam
|
||||
+ import stat
|
||||
+ nonrootuser = "wrsroot"
|
||||
+ with open(lockfile, 'w'):
|
||||
+ pass
|
||||
+ # must have the lock file with the correct group permissisions g+rw
|
||||
+ os.chmod(lockfile, stat.S_IRWXG | stat.S_IRWXU)
|
||||
+
|
||||
+
|
||||
self.set_password('keyring-setting', 'password reference',
|
||||
'password reference value')
|
||||
|
||||
@@ -257,15 +312,41 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
if not os.path.exists(self.file_path):
|
||||
return False
|
||||
self._migrate()
|
||||
- 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
|
||||
+
|
||||
+ # 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("keyringlock",external=True,lock_path="/tmp"):
|
||||
+ 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):
|
||||
+ # The current file doesn't have the keyring-setting, check the backup
|
||||
+ logging.warning("_check_file: The current file doesn't have the keyring-setting, check the backup")
|
||||
+ if os.path.exists(self.backup_file_path):
|
||||
+ config = configparser.RawConfigParser()
|
||||
+ config.read(self.backup_file_path)
|
||||
+ try:
|
||||
+ config.get(
|
||||
+ escape_for_ini('keyring-setting'),
|
||||
+ escape_for_ini('password reference'),
|
||||
+ )
|
||||
+ except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
+ return False
|
||||
+ # backup file has it, let's use it
|
||||
+ try:
|
||||
+ src = self.backup_file_path
|
||||
+ dest = self.file_path
|
||||
+ shutil.copy(src,dest)
|
||||
+ except shutil.Error as e:
|
||||
+ logging.warning("Revert from Backup failed. Error: %s" % e)
|
||||
+ return False
|
||||
+ else:
|
||||
+ return False
|
||||
+
|
||||
return True
|
||||
|
||||
def _unlock(self):
|
162
security/python-keyring/python-keyring/use_temporary_file.patch
Normal file
162
security/python-keyring/python-keyring/use_temporary_file.patch
Normal file
@ -0,0 +1,162 @@
|
||||
Index: keyring-5.3/keyring/backends/file.py
|
||||
===================================================================
|
||||
--- keyring-5.3.orig/keyring/backends/file.py
|
||||
+++ keyring-5.3/keyring/backends/file.py
|
||||
@@ -9,6 +9,7 @@ import abc
|
||||
import time
|
||||
import logging
|
||||
import shutil
|
||||
+import glob
|
||||
|
||||
from ..py27compat import configparser
|
||||
|
||||
@@ -36,13 +37,6 @@ class FileBacked(object):
|
||||
"""
|
||||
return os.path.join(platform_.data_root(), self.filename)
|
||||
|
||||
- @properties.NonDataProperty
|
||||
- def backup_file_path(self):
|
||||
- """
|
||||
- The path to the file where passwords are stored. This property
|
||||
- may be overridden by the subclass or at the instance level.
|
||||
- """
|
||||
- return os.path.join(platform_.data_root(), self.backup_filename)
|
||||
|
||||
class BaseKeyring(FileBacked, KeyringBackend):
|
||||
"""
|
||||
@@ -91,15 +85,6 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
return password
|
||||
|
||||
|
||||
- def filecopy(self,src,dest):
|
||||
- """copy file src to dest with default buffer size
|
||||
- """
|
||||
- with open(src, 'r') as f1:
|
||||
- with open(dest, 'w') as f2:
|
||||
- shutil.copyfileobj(f1,f2)
|
||||
- f2.flush()
|
||||
-
|
||||
-
|
||||
def set_password(self, service, username, password):
|
||||
"""Write the password in the file.
|
||||
"""
|
||||
@@ -125,23 +110,7 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(self.file_path)
|
||||
except configparser.ParsingError as e:
|
||||
- logging.warning("set_password: keyring file corrupted, Reverting to Backup")
|
||||
- # Revert to the backup file (copy backup over current file)
|
||||
- try:
|
||||
- src = self.backup_file_path
|
||||
- dest = self.file_path
|
||||
- self.filecopy(src,dest)
|
||||
- except shutil.Error as e:
|
||||
- logging.warning("set_password: Revert from Backup failed. Error: %s" % e)
|
||||
- raise
|
||||
- # Load the keyring from the disk, if this fails exception is raised
|
||||
- try:
|
||||
- config = configparser.RawConfigParser()
|
||||
- config.read(self.file_path)
|
||||
- except:
|
||||
- e = sys.exc_info()[0]
|
||||
- logging.warning("set_password: Both keyring files are non useable. Error: %s" % e)
|
||||
- raise
|
||||
+ logging.warning("set_password: keyring file corrupted")
|
||||
|
||||
|
||||
# Update the keyring with the password
|
||||
@@ -149,17 +118,15 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
config.add_section(service)
|
||||
config.set(service, username, password_base64)
|
||||
|
||||
- # Make a back up of the keyring file here
|
||||
- try:
|
||||
- src = self.file_path
|
||||
- dest = self.backup_file_path
|
||||
- self.filecopy(src,dest)
|
||||
- except shutil.Error as e:
|
||||
- logging.warning("set_password: Backup failed. Error: %s" % e)
|
||||
-
|
||||
# Save the keyring back to the file
|
||||
- with open(self.file_path, 'w') as config_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)
|
||||
|
||||
|
||||
|
||||
@@ -203,8 +170,15 @@ class BaseKeyring(FileBacked, KeyringBac
|
||||
except configparser.NoSectionError:
|
||||
raise PasswordDeleteError("Password not found")
|
||||
# update the file
|
||||
- with open(self.file_path, 'w') as config_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
|
||||
+ os.remove(storage_root + "/" + tmpfile)
|
||||
+
|
||||
|
||||
class PlaintextKeyring(BaseKeyring):
|
||||
"""Simple File Keyring with no encryption"""
|
||||
@@ -213,7 +187,6 @@ class PlaintextKeyring(BaseKeyring):
|
||||
"Applicable for all platforms, but not recommended"
|
||||
|
||||
filename = 'keyring_pass.cfg'
|
||||
- backup_filename = 'crypted_pass_backup.cfg'
|
||||
|
||||
def encrypt(self, password):
|
||||
"""Directly return the password itself.
|
||||
@@ -267,7 +240,6 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
"""PyCrypto File Keyring"""
|
||||
|
||||
filename = 'crypted_pass.cfg'
|
||||
- backup_filename = 'crypted_pass_backup.cfg'
|
||||
pw_prefix = 'pw:'.encode()
|
||||
|
||||
@properties.ClassProperty
|
||||
@@ -326,27 +298,15 @@ class EncryptedKeyring(Encrypted, BaseKe
|
||||
escape_for_ini('password reference'),
|
||||
)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
- # The current file doesn't have the keyring-setting, check the backup
|
||||
- if os.path.exists(self.backup_file_path):
|
||||
- config = configparser.RawConfigParser()
|
||||
- config.read(self.backup_file_path)
|
||||
- try:
|
||||
- config.get(
|
||||
- escape_for_ini('keyring-setting'),
|
||||
- escape_for_ini('password reference'),
|
||||
- )
|
||||
- except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
- return False
|
||||
- # backup file has it, let's use it
|
||||
- try:
|
||||
- src = self.backup_file_path
|
||||
- dest = self.file_path
|
||||
- shutil.copy(src,dest)
|
||||
- except shutil.Error as e:
|
||||
- logging.warning("Revert from Backup failed. Error: %s" % e)
|
||||
- return False
|
||||
- else:
|
||||
- return False
|
||||
+ 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")
|
||||
+
|
||||
|
||||
return True
|
||||
|
Loading…
Reference in New Issue
Block a user