lib/common: Switch data directories to fs::path
This commit is contained in:
parent
298980fa86
commit
a2ca7e29f5
|
@ -564,7 +564,7 @@ void MainWindow::startBarrier()
|
||||||
// launched the process (e.g. when launched with elevation). setting the
|
// launched the process (e.g. when launched with elevation). setting the
|
||||||
// profile dir on launch ensures it uses the same profile dir is used
|
// profile dir on launch ensures it uses the same profile dir is used
|
||||||
// no matter how its relaunched.
|
// no matter how its relaunched.
|
||||||
args << "--profile-dir" << QString::fromStdString("\"" + barrier::DataDirectories::profile() + "\"");
|
args << "--profile-dir" << QString::fromStdString("\"" + barrier::DataDirectories::profile().u8string() + "\"");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((barrierType() == barrierClient && !clientArgs(args, app))
|
if ((barrierType() == barrierClient && !clientArgs(args, app))
|
||||||
|
@ -1021,7 +1021,7 @@ void MainWindow::updateSSLFingerprint()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto local_path = barrier::DataDirectories::local_ssl_fingerprints_path();
|
auto local_path = barrier::DataDirectories::local_ssl_fingerprints_path();
|
||||||
if (!QFile::exists(QString::fromStdString(local_path))) {
|
if (!barrier::fs::exists(local_path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ App::initApp(int argc, const char** argv)
|
||||||
// parse command line
|
// parse command line
|
||||||
parseArgs(argc, argv);
|
parseArgs(argc, argv);
|
||||||
|
|
||||||
barrier::DataDirectories::profile(argsBase().m_profileDirectory.u8string());
|
barrier::DataDirectories::profile(argsBase().m_profileDirectory);
|
||||||
|
|
||||||
// set log filter
|
// set log filter
|
||||||
if (!CLOG->setFilter(argsBase().m_logFilter)) {
|
if (!CLOG->setFilter(argsBase().m_logFilter)) {
|
||||||
|
|
|
@ -130,11 +130,11 @@ ServerApp::help()
|
||||||
// refer to custom profile directory even if not saved yet
|
// refer to custom profile directory even if not saved yet
|
||||||
barrier::fs::path profile_path = argsBase().m_profileDirectory;
|
barrier::fs::path profile_path = argsBase().m_profileDirectory;
|
||||||
if (profile_path.empty()) {
|
if (profile_path.empty()) {
|
||||||
profile_path = barrier::fs::u8path(barrier::DataDirectories::profile());
|
profile_path = barrier::DataDirectories::profile();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto usr_config_path = (profile_path / barrier::fs::u8path(USR_CONFIG_NAME)).u8string();
|
auto usr_config_path = (profile_path / barrier::fs::u8path(USR_CONFIG_NAME)).u8string();
|
||||||
auto sys_config_path = (barrier::fs::u8path(barrier::DataDirectories::systemconfig()) /
|
auto sys_config_path = (barrier::DataDirectories::systemconfig() /
|
||||||
barrier::fs::u8path(SYS_CONFIG_NAME)).u8string();
|
barrier::fs::u8path(SYS_CONFIG_NAME)).u8string();
|
||||||
|
|
||||||
std::ostringstream buffer;
|
std::ostringstream buffer;
|
||||||
|
@ -197,7 +197,7 @@ ServerApp::loadConfig()
|
||||||
|
|
||||||
// load the default configuration if no explicit file given
|
// load the default configuration if no explicit file given
|
||||||
else {
|
else {
|
||||||
auto path = barrier::fs::u8path(barrier::DataDirectories::profile());
|
auto path = barrier::DataDirectories::profile();
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
// complete path
|
// complete path
|
||||||
path /= barrier::fs::u8path(USR_CONFIG_NAME);
|
path /= barrier::fs::u8path(USR_CONFIG_NAME);
|
||||||
|
@ -210,7 +210,7 @@ ServerApp::loadConfig()
|
||||||
}
|
}
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
// try the system-wide config file
|
// try the system-wide config file
|
||||||
path = barrier::fs::u8path(barrier::DataDirectories::systemconfig());
|
path = barrier::DataDirectories::systemconfig();
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
path /= barrier::fs::u8path(SYS_CONFIG_NAME);
|
path /= barrier::fs::u8path(SYS_CONFIG_NAME);
|
||||||
if (loadConfig(path.u8string())) {
|
if (loadConfig(path.u8string())) {
|
||||||
|
|
|
@ -245,7 +245,7 @@ DaemonApp::logFilename()
|
||||||
{
|
{
|
||||||
string logFilename = ARCH->setting("LogFilename");
|
string logFilename = ARCH->setting("LogFilename");
|
||||||
if (logFilename.empty())
|
if (logFilename.empty())
|
||||||
logFilename = barrier::DataDirectories::global() + "\\" + LOG_FILENAME;
|
logFilename = (barrier::DataDirectories::global() / LOG_FILENAME).u8string();
|
||||||
MSWindowsUtil::createDirectory(logFilename, true);
|
MSWindowsUtil::createDirectory(logFilename, true);
|
||||||
return logFilename;
|
return logFilename;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,30 +18,30 @@
|
||||||
#ifndef BARRIER_LIB_COMMON_DATA_DIRECTORIES_H
|
#ifndef BARRIER_LIB_COMMON_DATA_DIRECTORIES_H
|
||||||
#define BARRIER_LIB_COMMON_DATA_DIRECTORIES_H
|
#define BARRIER_LIB_COMMON_DATA_DIRECTORIES_H
|
||||||
|
|
||||||
#include <string>
|
#include "io/filesystem.h"
|
||||||
|
|
||||||
namespace barrier {
|
namespace barrier {
|
||||||
|
|
||||||
class DataDirectories
|
class DataDirectories
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const std::string& profile();
|
static const fs::path& profile();
|
||||||
static const std::string& profile(const std::string& path);
|
static const fs::path& profile(const fs::path& path);
|
||||||
|
|
||||||
static const std::string& global();
|
static const fs::path& global();
|
||||||
static const std::string& global(const std::string& path);
|
static const fs::path& global(const fs::path& path);
|
||||||
|
|
||||||
static const std::string& systemconfig();
|
static const fs::path& systemconfig();
|
||||||
static const std::string& systemconfig(const std::string& path);
|
static const fs::path& systemconfig(const fs::path& path);
|
||||||
|
|
||||||
static std::string ssl_fingerprints_path();
|
static fs::path ssl_fingerprints_path();
|
||||||
static std::string local_ssl_fingerprints_path();
|
static fs::path local_ssl_fingerprints_path();
|
||||||
static std::string trusted_servers_ssl_fingerprints_path();
|
static fs::path trusted_servers_ssl_fingerprints_path();
|
||||||
static std::string trusted_clients_ssl_fingerprints_path();
|
static fs::path trusted_clients_ssl_fingerprints_path();
|
||||||
private:
|
private:
|
||||||
static std::string _profile;
|
static fs::path _profile;
|
||||||
static std::string _global;
|
static fs::path _global;
|
||||||
static std::string _systemconfig;
|
static fs::path _systemconfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace barrier
|
} // namespace barrier
|
||||||
|
|
|
@ -19,33 +19,33 @@
|
||||||
|
|
||||||
namespace barrier {
|
namespace barrier {
|
||||||
|
|
||||||
std::string DataDirectories::_profile;
|
fs::path DataDirectories::_profile;
|
||||||
std::string DataDirectories::_global;
|
fs::path DataDirectories::_global;
|
||||||
std::string DataDirectories::_systemconfig;
|
fs::path DataDirectories::_systemconfig;
|
||||||
|
|
||||||
static const char kFingerprintsDirName[] = "SSL/Fingerprints";
|
static const char kFingerprintsDirName[] = "SSL/Fingerprints";
|
||||||
static const char kFingerprintsLocalFilename[] = "Local.txt";
|
static const char kFingerprintsLocalFilename[] = "Local.txt";
|
||||||
static const char kFingerprintsTrustedServersFilename[] = "TrustedServers.txt";
|
static const char kFingerprintsTrustedServersFilename[] = "TrustedServers.txt";
|
||||||
static const char kFingerprintsTrustedClientsFilename[] = "TrustedClients.txt";
|
static const char kFingerprintsTrustedClientsFilename[] = "TrustedClients.txt";
|
||||||
|
|
||||||
std::string DataDirectories::ssl_fingerprints_path()
|
fs::path DataDirectories::ssl_fingerprints_path()
|
||||||
{
|
{
|
||||||
return profile() + "/" + kFingerprintsDirName;
|
return profile() / kFingerprintsDirName;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DataDirectories::local_ssl_fingerprints_path()
|
fs::path DataDirectories::local_ssl_fingerprints_path()
|
||||||
{
|
{
|
||||||
return ssl_fingerprints_path() + "/" + kFingerprintsLocalFilename;
|
return ssl_fingerprints_path() / kFingerprintsLocalFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DataDirectories::trusted_servers_ssl_fingerprints_path()
|
fs::path DataDirectories::trusted_servers_ssl_fingerprints_path()
|
||||||
{
|
{
|
||||||
return ssl_fingerprints_path() + "/" + kFingerprintsTrustedServersFilename;
|
return ssl_fingerprints_path() / kFingerprintsTrustedServersFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DataDirectories::trusted_clients_ssl_fingerprints_path()
|
fs::path DataDirectories::trusted_clients_ssl_fingerprints_path()
|
||||||
{
|
{
|
||||||
return ssl_fingerprints_path() + "/" + kFingerprintsTrustedClientsFilename;
|
return ssl_fingerprints_path() / kFingerprintsTrustedClientsFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace barrier
|
} // namespace barrier
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
namespace barrier {
|
namespace barrier {
|
||||||
|
|
||||||
const std::string ProfileSubdir = "/barrier";
|
|
||||||
|
|
||||||
static std::string pw_dir(struct passwd* pwentp)
|
static std::string pw_dir(struct passwd* pwentp)
|
||||||
{
|
{
|
||||||
if (pwentp != NULL && pwentp->pw_dir != NULL)
|
if (pwentp != NULL && pwentp->pw_dir != NULL)
|
||||||
|
@ -35,7 +33,7 @@ static std::string pw_dir(struct passwd* pwentp)
|
||||||
|
|
||||||
#ifdef HAVE_GETPWUID_R
|
#ifdef HAVE_GETPWUID_R
|
||||||
|
|
||||||
static std::string unix_home()
|
static fs::path unix_home()
|
||||||
{
|
{
|
||||||
long size = -1;
|
long size = -1;
|
||||||
#if defined(_SC_GETPW_R_SIZE_MAX)
|
#if defined(_SC_GETPW_R_SIZE_MAX)
|
||||||
|
@ -48,47 +46,47 @@ static std::string unix_home()
|
||||||
struct passwd* pwentp;
|
struct passwd* pwentp;
|
||||||
std::string buffer(size, 0);
|
std::string buffer(size, 0);
|
||||||
getpwuid_r(getuid(), &pwent, &buffer[0], size, &pwentp);
|
getpwuid_r(getuid(), &pwent, &buffer[0], size, &pwentp);
|
||||||
return pw_dir(pwentp);
|
return fs::u8path(pw_dir(pwentp));
|
||||||
}
|
}
|
||||||
|
|
||||||
#else // not HAVE_GETPWUID_R
|
#else // not HAVE_GETPWUID_R
|
||||||
|
|
||||||
static std::string unix_home()
|
static fs::path unix_home()
|
||||||
{
|
{
|
||||||
return pw_dir(getpwuid(getuid()));
|
return fs::u8path(pw_dir(getpwuid(getuid())));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_GETPWUID_R
|
#endif // HAVE_GETPWUID_R
|
||||||
|
|
||||||
static std::string profile_basedir()
|
static fs::path profile_basedir()
|
||||||
{
|
{
|
||||||
#ifdef WINAPI_XWINDOWS
|
#ifdef WINAPI_XWINDOWS
|
||||||
// linux/bsd adheres to freedesktop standards
|
// linux/bsd adheres to freedesktop standards
|
||||||
// https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
// https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||||
const char* dir = std::getenv("XDG_DATA_HOME");
|
const char* dir = std::getenv("XDG_DATA_HOME");
|
||||||
if (dir != NULL)
|
if (dir != NULL)
|
||||||
return dir;
|
return fs::u8path(dir);
|
||||||
return unix_home() + "/.local/share";
|
return unix_home() / ".local/share";
|
||||||
#else
|
#else
|
||||||
// macos has its own standards
|
// macos has its own standards
|
||||||
// https://developer.apple.com/library/content/documentation/General/Conceptual/MOSXAppProgrammingGuide/AppRuntime/AppRuntime.html
|
// https://developer.apple.com/library/content/documentation/General/Conceptual/MOSXAppProgrammingGuide/AppRuntime/AppRuntime.html
|
||||||
return unix_home() + "/Library/Application Support";
|
return unix_home() / "Library/Application Support";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::profile()
|
const fs::path& DataDirectories::profile()
|
||||||
{
|
{
|
||||||
if (_profile.empty())
|
if (_profile.empty())
|
||||||
_profile = profile_basedir() + ProfileSubdir;
|
_profile = profile_basedir() / "barrier";
|
||||||
return _profile;
|
return _profile;
|
||||||
}
|
}
|
||||||
const std::string& DataDirectories::profile(const std::string& path)
|
const fs::path& DataDirectories::profile(const fs::path& path)
|
||||||
{
|
{
|
||||||
_profile = path;
|
_profile = path;
|
||||||
return _profile;
|
return _profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::global()
|
const fs::path& DataDirectories::global()
|
||||||
{
|
{
|
||||||
if (_global.empty())
|
if (_global.empty())
|
||||||
// TODO: where on a unix system should public/global shared data go?
|
// TODO: where on a unix system should public/global shared data go?
|
||||||
|
@ -96,20 +94,20 @@ const std::string& DataDirectories::global()
|
||||||
_global = "/tmp";
|
_global = "/tmp";
|
||||||
return _global;
|
return _global;
|
||||||
}
|
}
|
||||||
const std::string& DataDirectories::global(const std::string& path)
|
const fs::path& DataDirectories::global(const fs::path& path)
|
||||||
{
|
{
|
||||||
_global = path;
|
_global = path;
|
||||||
return _global;
|
return _global;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::systemconfig()
|
const fs::path& DataDirectories::systemconfig()
|
||||||
{
|
{
|
||||||
if (_systemconfig.empty())
|
if (_systemconfig.empty())
|
||||||
_systemconfig = "/etc";
|
_systemconfig = "/etc";
|
||||||
return _systemconfig;
|
return _systemconfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::systemconfig(const std::string& path)
|
const fs::path& DataDirectories::systemconfig(const fs::path& path)
|
||||||
{
|
{
|
||||||
_systemconfig = path;
|
_systemconfig = path;
|
||||||
return _systemconfig;
|
return _systemconfig;
|
||||||
|
|
|
@ -22,43 +22,43 @@
|
||||||
|
|
||||||
namespace barrier {
|
namespace barrier {
|
||||||
|
|
||||||
std::string known_folder_path(const KNOWNFOLDERID& id)
|
fs::path known_folder_path(const KNOWNFOLDERID& id)
|
||||||
{
|
{
|
||||||
std::string path;
|
fs::path path;
|
||||||
WCHAR* buffer;
|
WCHAR* buffer;
|
||||||
HRESULT result = SHGetKnownFolderPath(id, 0, NULL, &buffer);
|
HRESULT result = SHGetKnownFolderPath(id, 0, NULL, &buffer);
|
||||||
if (result == S_OK) {
|
if (result == S_OK) {
|
||||||
path = win_wchar_to_utf8(buffer);
|
path = fs::path(std::wstring(buffer));
|
||||||
CoTaskMemFree(buffer);
|
CoTaskMemFree(buffer);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::profile()
|
const fs::path& DataDirectories::profile()
|
||||||
{
|
{
|
||||||
if (_profile.empty())
|
if (_profile.empty())
|
||||||
_profile = known_folder_path(FOLDERID_LocalAppData) + "\\Barrier";
|
_profile = known_folder_path(FOLDERID_LocalAppData) / "Barrier";
|
||||||
return _profile;
|
return _profile;
|
||||||
}
|
}
|
||||||
const std::string& DataDirectories::profile(const std::string& path)
|
const fs::path& DataDirectories::profile(const fs::path& path)
|
||||||
{
|
{
|
||||||
_profile = path;
|
_profile = path;
|
||||||
return _profile;
|
return _profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::global()
|
const fs::path& DataDirectories::global()
|
||||||
{
|
{
|
||||||
if (_global.empty())
|
if (_global.empty())
|
||||||
_global = known_folder_path(FOLDERID_ProgramData) + "\\Barrier";
|
_global = known_folder_path(FOLDERID_ProgramData) / "Barrier";
|
||||||
return _global;
|
return _global;
|
||||||
}
|
}
|
||||||
const std::string& DataDirectories::global(const std::string& path)
|
const fs::path& DataDirectories::global(const fs::path& path)
|
||||||
{
|
{
|
||||||
_global = path;
|
_global = path;
|
||||||
return _global;
|
return _global;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::systemconfig()
|
const fs::path& DataDirectories::systemconfig()
|
||||||
{
|
{
|
||||||
// systemconfig() is a special case in that it will track the current value
|
// systemconfig() is a special case in that it will track the current value
|
||||||
// of global() unless and until it is explicitly set otherwise
|
// of global() unless and until it is explicitly set otherwise
|
||||||
|
@ -68,7 +68,7 @@ const std::string& DataDirectories::systemconfig()
|
||||||
return _systemconfig;
|
return _systemconfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& DataDirectories::systemconfig(const std::string& path)
|
const fs::path& DataDirectories::systemconfig(const fs::path& path)
|
||||||
{
|
{
|
||||||
_systemconfig = path;
|
_systemconfig = path;
|
||||||
return _systemconfig;
|
return _systemconfig;
|
||||||
|
|
|
@ -23,14 +23,14 @@
|
||||||
|
|
||||||
namespace barrier {
|
namespace barrier {
|
||||||
|
|
||||||
void FingerprintDatabase::read(const std::string& path)
|
void FingerprintDatabase::read(const fs::path& path)
|
||||||
{
|
{
|
||||||
std::ifstream file;
|
std::ifstream file;
|
||||||
open_utf8_path(file, path, std::ios_base::in);
|
open_utf8_path(file, path, std::ios_base::in);
|
||||||
read_stream(file);
|
read_stream(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FingerprintDatabase::write(const std::string& path)
|
void FingerprintDatabase::write(const fs::path& path)
|
||||||
{
|
{
|
||||||
std::ofstream file;
|
std::ofstream file;
|
||||||
open_utf8_path(file, path, std::ios_base::out);
|
open_utf8_path(file, path, std::ios_base::out);
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#define BARRIER_LIB_NET_FINGERPRINT_DATABASE_H
|
#define BARRIER_LIB_NET_FINGERPRINT_DATABASE_H
|
||||||
|
|
||||||
#include "FingerprintData.h"
|
#include "FingerprintData.h"
|
||||||
|
#include "io/filesystem.h"
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -27,8 +28,8 @@ namespace barrier {
|
||||||
|
|
||||||
class FingerprintDatabase {
|
class FingerprintDatabase {
|
||||||
public:
|
public:
|
||||||
void read(const std::string& path);
|
void read(const fs::path& path);
|
||||||
void write(const std::string& path);
|
void write(const fs::path& path);
|
||||||
|
|
||||||
void read_stream(std::istream& stream);
|
void read_stream(std::istream& stream);
|
||||||
void write_stream(std::ostream& stream);
|
void write_stream(std::ostream& stream);
|
||||||
|
|
|
@ -677,17 +677,17 @@ SecureSocket::verifyCertFingerprint()
|
||||||
auto fingerprint_db_path = barrier::DataDirectories::trusted_servers_ssl_fingerprints_path();
|
auto fingerprint_db_path = barrier::DataDirectories::trusted_servers_ssl_fingerprints_path();
|
||||||
|
|
||||||
// Provide debug hint as to what file is being used to verify fingerprint trust
|
// Provide debug hint as to what file is being used to verify fingerprint trust
|
||||||
LOG((CLOG_NOTE "fingerprint_db_path: %s", fingerprint_db_path.c_str()));
|
LOG((CLOG_NOTE "fingerprint_db_path: %s", fingerprint_db_path.u8string().c_str()));
|
||||||
|
|
||||||
barrier::FingerprintDatabase db;
|
barrier::FingerprintDatabase db;
|
||||||
db.read(fingerprint_db_path);
|
db.read(fingerprint_db_path);
|
||||||
|
|
||||||
if (!db.fingerprints().empty()) {
|
if (!db.fingerprints().empty()) {
|
||||||
LOG((CLOG_NOTE "Read %d fingerprints from: %s", db.fingerprints().size(),
|
LOG((CLOG_NOTE "Read %d fingerprints from: %s", db.fingerprints().size(),
|
||||||
fingerprint_db_path.c_str()));
|
fingerprint_db_path.u8string().c_str()));
|
||||||
} else {
|
} else {
|
||||||
LOG((CLOG_NOTE "Could not read fingerprints from: %s",
|
LOG((CLOG_NOTE "Could not read fingerprints from: %s",
|
||||||
fingerprint_db_path.c_str()));
|
fingerprint_db_path.u8string().c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (db.is_trusted(fingerprint_sha256)) {
|
if (db.is_trusted(fingerprint_sha256)) {
|
||||||
|
|
|
@ -574,7 +574,7 @@ MSWindowsHook::install()
|
||||||
g_fakeServerInput = false;
|
g_fakeServerInput = false;
|
||||||
|
|
||||||
// setup immune keys
|
// setup immune keys
|
||||||
g_immuneKeysPath = barrier::DataDirectories::profile() + "\\ImmuneKeys.txt";
|
g_immuneKeysPath = (barrier::DataDirectories::profile() / "ImmuneKeys.txt").u8string();
|
||||||
g_immuneKeys = immune_keys_list();
|
g_immuneKeys = immune_keys_list();
|
||||||
LOG((CLOG_DEBUG "Found %u immune keys in %s", g_immuneKeys.size(), g_immuneKeysPath.c_str()));
|
LOG((CLOG_DEBUG "Found %u immune keys in %s", g_immuneKeys.size(), g_immuneKeysPath.c_str()));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue