mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-05-21 21:30:48 -05:00
macOS: run APFS formatter elevated
APFS volume creation can still fail with Permission denied after preparing the raw and block device aliases because newfs_apfs performs privileged APFS container and volume operations beyond opening the device nodes. Route APFS formatting through the elevated CoreService path for non-root macOS runs. Keep the elevated interface narrow by sending only the target device and invoking user UID/GID, validate the device path on the privileged side, rebuild the formatter arguments there, and execute /sbin/newfs_apfs by absolute path to avoid PATH shadowing. Pass -U/-G so the created filesystem preserves the invoking user ownership. Apply the same path to GUI and text-mode creation.
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
#include "Platform/SystemLog.h"
|
#include "Platform/SystemLog.h"
|
||||||
#include "Platform/Thread.h"
|
#include "Platform/Thread.h"
|
||||||
#include "Platform/Unix/Poller.h"
|
#include "Platform/Unix/Poller.h"
|
||||||
|
#include "Platform/Unix/Process.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "CoreUnix.h"
|
#include "CoreUnix.h"
|
||||||
#include "CoreServiceRequest.h"
|
#include "CoreServiceRequest.h"
|
||||||
@@ -27,6 +28,66 @@
|
|||||||
|
|
||||||
namespace VeraCrypt
|
namespace VeraCrypt
|
||||||
{
|
{
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
static bool IsMacOSXDevicePathWithPrefix (const string &path, const string &prefix)
|
||||||
|
{
|
||||||
|
if (path.find (prefix) != 0 || path.size() <= prefix.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t index = prefix.size();
|
||||||
|
while (index < path.size() && path[index] >= '0' && path[index] <= '9')
|
||||||
|
++index;
|
||||||
|
|
||||||
|
if (index == prefix.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (index == path.size())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (path[index++] != 's')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t sliceStart = index;
|
||||||
|
while (index < path.size() && path[index] >= '0' && path[index] <= '9')
|
||||||
|
++index;
|
||||||
|
|
||||||
|
return index > sliceStart && index == path.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsMacOSXFormatterDevicePath (const string &path)
|
||||||
|
{
|
||||||
|
return IsMacOSXDevicePathWithPrefix (path, "/dev/disk")
|
||||||
|
|| IsMacOSXDevicePathWithPrefix (path, "/dev/rdisk");
|
||||||
|
}
|
||||||
|
|
||||||
|
static list <string> BuildMacOSXAPFSFormatterArguments (const ExecuteMacOSXAPFSFormatterRequest &request)
|
||||||
|
{
|
||||||
|
if (!IsMacOSXFormatterDevicePath (request.Device))
|
||||||
|
throw ParameterIncorrect (SRC_POS);
|
||||||
|
|
||||||
|
if (request.OwnerUserId > static_cast <uint64> ((uid_t) -1)
|
||||||
|
|| request.OwnerGroupId > static_cast <uint64> ((gid_t) -1))
|
||||||
|
{
|
||||||
|
throw ParameterIncorrect (SRC_POS);
|
||||||
|
}
|
||||||
|
|
||||||
|
stringstream uid;
|
||||||
|
stringstream gid;
|
||||||
|
list <string> arguments;
|
||||||
|
|
||||||
|
uid << request.OwnerUserId;
|
||||||
|
gid << request.OwnerGroupId;
|
||||||
|
|
||||||
|
arguments.push_back ("-U");
|
||||||
|
arguments.push_back (uid.str());
|
||||||
|
arguments.push_back ("-G");
|
||||||
|
arguments.push_back (gid.str());
|
||||||
|
arguments.push_back (string (request.Device));
|
||||||
|
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
unique_ptr <T> CoreService::GetResponse ()
|
unique_ptr <T> CoreService::GetResponse ()
|
||||||
{
|
{
|
||||||
@@ -201,6 +262,17 @@ namespace VeraCrypt
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
// ExecuteMacOSXAPFSFormatterRequest
|
||||||
|
ExecuteMacOSXAPFSFormatterRequest *executeAPFSFormatterRequest = dynamic_cast <ExecuteMacOSXAPFSFormatterRequest*> (request.get());
|
||||||
|
if (executeAPFSFormatterRequest)
|
||||||
|
{
|
||||||
|
Process::Execute (CoreService::GetMacOSXAPFSFormatterPath(), BuildMacOSXAPFSFormatterArguments (*executeAPFSFormatterRequest));
|
||||||
|
ExecuteMacOSXAPFSFormatterResponse().Serialize (outputStream);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MountVolumeRequest
|
// MountVolumeRequest
|
||||||
MountVolumeRequest *mountRequest = dynamic_cast <MountVolumeRequest*> (request.get());
|
MountVolumeRequest *mountRequest = dynamic_cast <MountVolumeRequest*> (request.get());
|
||||||
if (mountRequest)
|
if (mountRequest)
|
||||||
@@ -290,6 +362,19 @@ namespace VeraCrypt
|
|||||||
return SendRequest <GetHostDevicesResponse> (request)->HostDevices;
|
return SendRequest <GetHostDevicesResponse> (request)->HostDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
const char *CoreService::GetMacOSXAPFSFormatterPath ()
|
||||||
|
{
|
||||||
|
return "/sbin/newfs_apfs";
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreService::RequestExecuteMacOSXAPFSFormatter (const DevicePath &devicePath, uint64 userId, uint64 groupId)
|
||||||
|
{
|
||||||
|
ExecuteMacOSXAPFSFormatterRequest request (devicePath, userId, groupId);
|
||||||
|
SendRequest <ExecuteMacOSXAPFSFormatterResponse> (request);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
shared_ptr <VolumeInfo> CoreService::RequestMountVolume (MountOptions &options)
|
shared_ptr <VolumeInfo> CoreService::RequestMountVolume (MountOptions &options)
|
||||||
{
|
{
|
||||||
MountVolumeRequest request (&options);
|
MountVolumeRequest request (&options);
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ namespace VeraCrypt
|
|||||||
static uint32 RequestGetDeviceSectorSize (const DevicePath &devicePath);
|
static uint32 RequestGetDeviceSectorSize (const DevicePath &devicePath);
|
||||||
static uint64 RequestGetDeviceSize (const DevicePath &devicePath);
|
static uint64 RequestGetDeviceSize (const DevicePath &devicePath);
|
||||||
static HostDeviceList RequestGetHostDevices (bool pathListOnly);
|
static HostDeviceList RequestGetHostDevices (bool pathListOnly);
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
static const char *GetMacOSXAPFSFormatterPath ();
|
||||||
|
static void RequestExecuteMacOSXAPFSFormatter (const DevicePath &devicePath, uint64 userId, uint64 groupId);
|
||||||
|
#endif
|
||||||
static shared_ptr <VolumeInfo> RequestMountVolume (MountOptions &options);
|
static shared_ptr <VolumeInfo> RequestMountVolume (MountOptions &options);
|
||||||
static void RequestSetFileOwner (const FilesystemPath &path, const UserId &owner);
|
static void RequestSetFileOwner (const FilesystemPath &path, const UserId &owner);
|
||||||
static void SetAdminPasswordCallback (shared_ptr <GetStringFunctor> functor) { AdminPasswordCallback = functor; }
|
static void SetAdminPasswordCallback (shared_ptr <GetStringFunctor> functor) { AdminPasswordCallback = functor; }
|
||||||
|
|||||||
@@ -219,6 +219,32 @@ namespace VeraCrypt
|
|||||||
CoreServiceRequest::Serialize (stream);
|
CoreServiceRequest::Serialize (stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
// ExecuteMacOSXAPFSFormatterRequest
|
||||||
|
void ExecuteMacOSXAPFSFormatterRequest::Deserialize (shared_ptr <Stream> stream)
|
||||||
|
{
|
||||||
|
CoreServiceRequest::Deserialize (stream);
|
||||||
|
Serializer sr (stream);
|
||||||
|
Device = sr.DeserializeWString ("Device");
|
||||||
|
sr.Deserialize ("OwnerGroupId", OwnerGroupId);
|
||||||
|
sr.Deserialize ("OwnerUserId", OwnerUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteMacOSXAPFSFormatterRequest::RequiresElevation () const
|
||||||
|
{
|
||||||
|
return !Core->HasAdminPrivileges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExecuteMacOSXAPFSFormatterRequest::Serialize (shared_ptr <Stream> stream) const
|
||||||
|
{
|
||||||
|
CoreServiceRequest::Serialize (stream);
|
||||||
|
Serializer sr (stream);
|
||||||
|
sr.Serialize ("Device", wstring (Device));
|
||||||
|
sr.Serialize ("OwnerGroupId", OwnerGroupId);
|
||||||
|
sr.Serialize ("OwnerUserId", OwnerUserId);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MountVolumeRequest
|
// MountVolumeRequest
|
||||||
void MountVolumeRequest::Deserialize (shared_ptr <Stream> stream)
|
void MountVolumeRequest::Deserialize (shared_ptr <Stream> stream)
|
||||||
{
|
{
|
||||||
@@ -294,6 +320,9 @@ namespace VeraCrypt
|
|||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (EmergencyDismountVolumeRequest);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (EmergencyDismountVolumeRequest);
|
||||||
#endif
|
#endif
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (ExitRequest);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (ExitRequest);
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
TC_SERIALIZER_FACTORY_ADD_CLASS (ExecuteMacOSXAPFSFormatterRequest);
|
||||||
|
#endif
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSectorSizeRequest);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSectorSizeRequest);
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSizeRequest);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSizeRequest);
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetHostDevicesRequest);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetHostDevicesRequest);
|
||||||
|
|||||||
@@ -126,6 +126,22 @@ namespace VeraCrypt
|
|||||||
TC_SERIALIZABLE (ExitRequest);
|
TC_SERIALIZABLE (ExitRequest);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
struct ExecuteMacOSXAPFSFormatterRequest : CoreServiceRequest
|
||||||
|
{
|
||||||
|
ExecuteMacOSXAPFSFormatterRequest () { }
|
||||||
|
ExecuteMacOSXAPFSFormatterRequest (const DevicePath &devicePath, uint64 userId, uint64 groupId)
|
||||||
|
: Device (devicePath), OwnerGroupId (groupId), OwnerUserId (userId) { }
|
||||||
|
TC_SERIALIZABLE (ExecuteMacOSXAPFSFormatterRequest);
|
||||||
|
|
||||||
|
virtual bool RequiresElevation () const;
|
||||||
|
|
||||||
|
DevicePath Device;
|
||||||
|
uint64 OwnerGroupId;
|
||||||
|
uint64 OwnerUserId;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
struct MountVolumeRequest : CoreServiceRequest
|
struct MountVolumeRequest : CoreServiceRequest
|
||||||
{
|
{
|
||||||
MountVolumeRequest () { }
|
MountVolumeRequest () { }
|
||||||
|
|||||||
@@ -88,6 +88,18 @@ namespace VeraCrypt
|
|||||||
Serializable::SerializeList (stream, HostDevices);
|
Serializable::SerializeList (stream, HostDevices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
// ExecuteMacOSXAPFSFormatterResponse
|
||||||
|
void ExecuteMacOSXAPFSFormatterResponse::Deserialize (shared_ptr <Stream> stream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExecuteMacOSXAPFSFormatterResponse::Serialize (shared_ptr <Stream> stream) const
|
||||||
|
{
|
||||||
|
Serializable::Serialize (stream);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MountVolumeResponse
|
// MountVolumeResponse
|
||||||
void MountVolumeResponse::Deserialize (shared_ptr <Stream> stream)
|
void MountVolumeResponse::Deserialize (shared_ptr <Stream> stream)
|
||||||
{
|
{
|
||||||
@@ -118,6 +130,9 @@ namespace VeraCrypt
|
|||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSectorSizeResponse);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSectorSizeResponse);
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSizeResponse);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetDeviceSizeResponse);
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (GetHostDevicesResponse);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (GetHostDevicesResponse);
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
TC_SERIALIZER_FACTORY_ADD_CLASS (ExecuteMacOSXAPFSFormatterResponse);
|
||||||
|
#endif
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (MountVolumeResponse);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (MountVolumeResponse);
|
||||||
TC_SERIALIZER_FACTORY_ADD_CLASS (SetFileOwnerResponse);
|
TC_SERIALIZER_FACTORY_ADD_CLASS (SetFileOwnerResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,14 @@ namespace VeraCrypt
|
|||||||
HostDeviceList HostDevices;
|
HostDeviceList HostDevices;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
struct ExecuteMacOSXAPFSFormatterResponse : CoreServiceResponse
|
||||||
|
{
|
||||||
|
ExecuteMacOSXAPFSFormatterResponse () { }
|
||||||
|
TC_SERIALIZABLE (ExecuteMacOSXAPFSFormatterResponse);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
struct MountVolumeResponse : CoreServiceResponse
|
struct MountVolumeResponse : CoreServiceResponse
|
||||||
{
|
{
|
||||||
MountVolumeResponse () { }
|
MountVolumeResponse () { }
|
||||||
|
|||||||
@@ -843,7 +843,9 @@ namespace VeraCrypt
|
|||||||
{
|
{
|
||||||
RestoreMacOSXFormatterDeviceOwners (*finally_arg);
|
RestoreMacOSXFormatterDeviceOwners (*finally_arg);
|
||||||
});
|
});
|
||||||
PrepareMacOSXFormatterDevice (virtualDevice, changedDeviceOwners);
|
bool useElevatedAPFSFormatter = UseElevatedMacOSXAPFSFormatter (fsFormatter);
|
||||||
|
if (!useElevatedAPFSFormatter)
|
||||||
|
PrepareMacOSXFormatterDevice (virtualDevice, changedDeviceOwners);
|
||||||
#else
|
#else
|
||||||
UserId origDeviceOwner ((uid_t) -1);
|
UserId origDeviceOwner ((uid_t) -1);
|
||||||
|
|
||||||
@@ -888,10 +890,19 @@ namespace VeraCrypt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
if (IsMacOSXAPFSFormatter (fsFormatter) && !useElevatedAPFSFormatter)
|
||||||
|
AddMacOSXAPFSFormatterUserArgs (args);
|
||||||
|
#endif
|
||||||
|
|
||||||
args.push_back (string (virtualDevice));
|
args.push_back (string (virtualDevice));
|
||||||
|
|
||||||
SetCreationProgressText (StringFormatter (LangString["FORMAT_STAGE_CREATING_FILESYSTEM"], fsFormatter));
|
SetCreationProgressText (StringFormatter (LangString["FORMAT_STAGE_CREATING_FILESYSTEM"], fsFormatter));
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
ExecuteMacOSXFilesystemFormatter (fsFormatter, args);
|
||||||
|
#else
|
||||||
Process::Execute (fsFormatter, args);
|
Process::Execute (fsFormatter, args);
|
||||||
|
#endif
|
||||||
SetCreationProgressText (LangString["FORMAT_STAGE_DISMOUNTING_TEMP_VOLUME"]);
|
SetCreationProgressText (LangString["FORMAT_STAGE_DISMOUNTING_TEMP_VOLUME"]);
|
||||||
}
|
}
|
||||||
#endif // TC_UNIX
|
#endif // TC_UNIX
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#ifdef TC_MACOSX
|
#ifdef TC_MACOSX
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "Core/Unix/CoreService.h"
|
||||||
|
#include "Platform/Unix/Process.h"
|
||||||
|
|
||||||
namespace VeraCrypt
|
namespace VeraCrypt
|
||||||
{
|
{
|
||||||
@@ -59,6 +61,33 @@ namespace VeraCrypt
|
|||||||
return deviceIdentifier;
|
return deviceIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool IsMacOSXAPFSFormatter (const string &fsFormatter)
|
||||||
|
{
|
||||||
|
size_t namePos = fsFormatter.find_last_of ('/');
|
||||||
|
string fsFormatterName = namePos == string::npos ? fsFormatter : fsFormatter.substr (namePos + 1);
|
||||||
|
return fsFormatterName == "newfs_apfs";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool UseElevatedMacOSXAPFSFormatter (const string &fsFormatter)
|
||||||
|
{
|
||||||
|
return IsMacOSXAPFSFormatter (fsFormatter) && !Core->HasAdminPrivileges();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddMacOSXAPFSFormatterUserArgs (list <string> &args)
|
||||||
|
{
|
||||||
|
stringstream uid;
|
||||||
|
stringstream gid;
|
||||||
|
|
||||||
|
// The APFS formatter may run elevated, so preserve the invoking user's ownership.
|
||||||
|
uid << getuid();
|
||||||
|
gid << getgid();
|
||||||
|
|
||||||
|
args.push_back ("-U");
|
||||||
|
args.push_back (uid.str());
|
||||||
|
args.push_back ("-G");
|
||||||
|
args.push_back (gid.str());
|
||||||
|
}
|
||||||
|
|
||||||
struct MacOSXFormatterDeviceOwnerRestore
|
struct MacOSXFormatterDeviceOwnerRestore
|
||||||
{
|
{
|
||||||
MacOSXFormatterDeviceOwnerRestore (const FilesystemPath &path, const UserId &owner)
|
MacOSXFormatterDeviceOwnerRestore (const FilesystemPath &path, const UserId &owner)
|
||||||
@@ -125,6 +154,20 @@ namespace VeraCrypt
|
|||||||
catch (...) { }
|
catch (...) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void ExecuteMacOSXFilesystemFormatter (const string &fsFormatter, const list <string> &args)
|
||||||
|
{
|
||||||
|
if (UseElevatedMacOSXAPFSFormatter (fsFormatter))
|
||||||
|
{
|
||||||
|
if (args.empty())
|
||||||
|
throw ParameterIncorrect (SRC_POS);
|
||||||
|
|
||||||
|
CoreService::RequestExecuteMacOSXAPFSFormatter (DevicePath (args.back()), getuid(), getgid());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process::Execute (IsMacOSXAPFSFormatter (fsFormatter) ? CoreService::GetMacOSXAPFSFormatterPath() : fsFormatter, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif // TC_MACOSX
|
#endif // TC_MACOSX
|
||||||
|
|
||||||
|
|||||||
@@ -1078,7 +1078,9 @@ namespace VeraCrypt
|
|||||||
{
|
{
|
||||||
RestoreMacOSXFormatterDeviceOwners (*finally_arg);
|
RestoreMacOSXFormatterDeviceOwners (*finally_arg);
|
||||||
});
|
});
|
||||||
PrepareMacOSXFormatterDevice (virtualDevice, changedDeviceOwners);
|
bool useElevatedAPFSFormatter = UseElevatedMacOSXAPFSFormatter (fsFormatter);
|
||||||
|
if (!useElevatedAPFSFormatter)
|
||||||
|
PrepareMacOSXFormatterDevice (virtualDevice, changedDeviceOwners);
|
||||||
#else
|
#else
|
||||||
UserId origDeviceOwner ((uid_t) -1);
|
UserId origDeviceOwner ((uid_t) -1);
|
||||||
|
|
||||||
@@ -1123,9 +1125,18 @@ namespace VeraCrypt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
if (IsMacOSXAPFSFormatter (fsFormatter) && !useElevatedAPFSFormatter)
|
||||||
|
AddMacOSXAPFSFormatterUserArgs (args);
|
||||||
|
#endif
|
||||||
|
|
||||||
args.push_back (string (virtualDevice));
|
args.push_back (string (virtualDevice));
|
||||||
|
|
||||||
|
#ifdef TC_MACOSX
|
||||||
|
ExecuteMacOSXFilesystemFormatter (fsFormatter, args);
|
||||||
|
#else
|
||||||
Process::Execute (fsFormatter, args);
|
Process::Execute (fsFormatter, args);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif // TC_UNIX
|
#endif // TC_UNIX
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user