iscsi: Remove redundant repetition from ACL
The results of access control procedure for login in the spdk_iscsi_tgt_node_access() is defined in the following table: +------------------------------+ |iscsi name |netmask |result | +------------------------------+ +------------------------------+ |denied |- |denied | +------------------------------+ |allowed |allowed |allowed | +------------------------------+ |allowed |denied |next IG | +------------------------------+ |not found |- |next IG | +------------------------------+ However current implementation have redundant repetition in the spdk_iscsi_tgt_node_access() and the above definition is not visible. Hence refactor spdk_iscsi_tgt_node_access(). Besides refactor spdk_iscsi_tgt_node_allow_iscsi_name() because it has redundant repetition too. Add UT code for these changes. Change-Id: If06d87c1246c85439ee0482149bd887a7b53b169 Signed-off-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com> Reviewed-on: https://review.gerrithub.io/379935 Tested-by: SPDK Automated Test System <sys_sgsw@intel.com> Reviewed-by: Jim Harris <james.r.harris@intel.com> Reviewed-by: Daniel Verkamp <daniel.verkamp@intel.com>
This commit is contained in:
parent
1674f37ac9
commit
c2f38258f9
@ -174,15 +174,55 @@ spdk_iscsi_netmask_allow_addr(const char *netmask, const char *addr)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
spdk_iscsi_init_grp_allow_addr(struct spdk_iscsi_init_grp *igp,
|
||||||
|
const char *addr)
|
||||||
|
{
|
||||||
|
struct spdk_iscsi_initiator_netmask *imask;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(imask, &igp->netmask_head, tailq) {
|
||||||
|
SPDK_DEBUGLOG(SPDK_TRACE_ISCSI, "netmask=%s, addr=%s\n",
|
||||||
|
imask->mask, addr);
|
||||||
|
if (spdk_iscsi_netmask_allow_addr(imask->mask, addr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
spdk_iscsi_init_grp_allow_iscsi_name(struct spdk_iscsi_init_grp *igp,
|
||||||
|
const char *iqn, bool *result)
|
||||||
|
{
|
||||||
|
struct spdk_iscsi_initiator_name *iname;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(iname, &igp->initiator_head, tailq) {
|
||||||
|
/* denied if iqn is matched */
|
||||||
|
if ((iname->name[0] == '!')
|
||||||
|
&& (strcasecmp(&iname->name[1], "ANY") == 0
|
||||||
|
|| strcasecmp(&iname->name[1], iqn) == 0)) {
|
||||||
|
*result = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* allowed if iqn is matched */
|
||||||
|
if (strcasecmp(iname->name, "ANY") == 0
|
||||||
|
|| strcasecmp(iname->name, iqn) == 0) {
|
||||||
|
*result = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn,
|
spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn,
|
||||||
struct spdk_iscsi_tgt_node *target, const char *iqn, const char *addr)
|
struct spdk_iscsi_tgt_node *target, const char *iqn, const char *addr)
|
||||||
{
|
{
|
||||||
struct spdk_iscsi_portal_grp *pg;
|
struct spdk_iscsi_portal_grp *pg;
|
||||||
struct spdk_iscsi_init_grp *igp;
|
struct spdk_iscsi_init_grp *igp;
|
||||||
struct spdk_iscsi_initiator_name *iname;
|
|
||||||
struct spdk_iscsi_initiator_netmask *imask;
|
|
||||||
int i;
|
int i;
|
||||||
|
int rc;
|
||||||
|
bool allowed = false;
|
||||||
|
|
||||||
if (conn == NULL || target == NULL || iqn == NULL || addr == NULL)
|
if (conn == NULL || target == NULL || iqn == NULL || addr == NULL)
|
||||||
return false;
|
return false;
|
||||||
@ -195,27 +235,17 @@ spdk_iscsi_tgt_node_access(struct spdk_iscsi_conn *conn,
|
|||||||
if (pg != target->map[i].pg)
|
if (pg != target->map[i].pg)
|
||||||
continue;
|
continue;
|
||||||
igp = target->map[i].ig;
|
igp = target->map[i].ig;
|
||||||
TAILQ_FOREACH(iname, &igp->initiator_head, tailq) {
|
rc = spdk_iscsi_init_grp_allow_iscsi_name(igp, iqn, &allowed);
|
||||||
/* denied if iqn is matched */
|
if (rc == 0) {
|
||||||
if ((iname->name[0] == '!')
|
if (allowed == false) {
|
||||||
&& (strcasecmp(&iname->name[1], "ANY") == 0
|
|
||||||
|| strcasecmp(&iname->name[1], iqn) == 0)) {
|
|
||||||
goto denied;
|
goto denied;
|
||||||
}
|
} else {
|
||||||
/* allowed if iqn is matched */
|
if (spdk_iscsi_init_grp_allow_addr(igp, addr)) {
|
||||||
if (strcasecmp(iname->name, "ANY") == 0
|
return true;
|
||||||
|| strcasecmp(iname->name, iqn) == 0) {
|
|
||||||
/* iqn is allowed, then check netmask */
|
|
||||||
TAILQ_FOREACH(imask, &igp->netmask_head, tailq) {
|
|
||||||
SPDK_DEBUGLOG(SPDK_TRACE_ISCSI,
|
|
||||||
"netmask=%s, addr=%s\n",
|
|
||||||
imask->mask, addr);
|
|
||||||
if (spdk_iscsi_netmask_allow_addr(imask->mask, addr)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* netmask is denied in this initiator group */
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
/* netmask is denied in this initiator group */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,25 +260,27 @@ static bool
|
|||||||
spdk_iscsi_tgt_node_allow_iscsi_name(struct spdk_iscsi_tgt_node *target, const char *iqn)
|
spdk_iscsi_tgt_node_allow_iscsi_name(struct spdk_iscsi_tgt_node *target, const char *iqn)
|
||||||
{
|
{
|
||||||
struct spdk_iscsi_init_grp *igp;
|
struct spdk_iscsi_init_grp *igp;
|
||||||
struct spdk_iscsi_initiator_name *iname;
|
int i, j;
|
||||||
int i;
|
int rc;
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
if (target == NULL || iqn == NULL)
|
if (target == NULL || iqn == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (i = 0; i < target->maxmap; i++) {
|
for (i = 0; i < target->maxmap; i++) {
|
||||||
igp = target->map[i].ig;
|
igp = target->map[i].ig;
|
||||||
TAILQ_FOREACH(iname, &igp->initiator_head, tailq) {
|
/* skip same ig_tag */
|
||||||
if ((iname->name[0] == '!')
|
for (j = 0; j < i; j++) {
|
||||||
&& (strcasecmp(&iname->name[1], "ANY") == 0
|
if (target->map[j].ig->tag == igp->tag) {
|
||||||
|| strcasecmp(&iname->name[1], iqn) == 0)) {
|
goto skip_ig_tag;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (strcasecmp(iname->name, "ANY") == 0
|
|
||||||
|| strcasecmp(iname->name, iqn) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rc = spdk_iscsi_init_grp_allow_iscsi_name(igp, iqn, &result);
|
||||||
|
if (rc == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
skip_ig_tag:
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -284,6 +284,296 @@ node_access_denied_by_empty_netmask(void)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define IQN1 "iqn.2017-11.spdk.io:0001"
|
||||||
|
#define NO_IQN1 "!iqn.2017-11.spdk.io:0001"
|
||||||
|
#define IQN2 "iqn.2017-11.spdk.io:0002"
|
||||||
|
#define IP1 "192.168.2.0"
|
||||||
|
#define IP2 "192.168.2.1"
|
||||||
|
|
||||||
|
static void
|
||||||
|
node_access_multi_initiator_groups_cases(void)
|
||||||
|
{
|
||||||
|
struct spdk_iscsi_tgt_node tgtnode;
|
||||||
|
struct spdk_iscsi_conn conn;
|
||||||
|
struct spdk_iscsi_portal_grp pg;
|
||||||
|
struct spdk_iscsi_portal portal;
|
||||||
|
struct spdk_iscsi_init_grp ig1, ig2;
|
||||||
|
struct spdk_iscsi_initiator_name iname1, iname2;
|
||||||
|
struct spdk_iscsi_initiator_netmask imask1, imask2;
|
||||||
|
char *iqn, *addr;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
/* target initialization */
|
||||||
|
memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
|
||||||
|
tgtnode.maxmap = 2;
|
||||||
|
tgtnode.name = IQN1;
|
||||||
|
|
||||||
|
tgtnode.map[0].pg = &pg;
|
||||||
|
tgtnode.map[0].ig = &ig1;
|
||||||
|
tgtnode.map[1].pg = &pg;
|
||||||
|
tgtnode.map[1].ig = &ig2;
|
||||||
|
|
||||||
|
/* portal group initialization */
|
||||||
|
memset(&pg, 0, sizeof(struct spdk_iscsi_portal_grp));
|
||||||
|
pg.tag = 1;
|
||||||
|
|
||||||
|
/* portal initialization */
|
||||||
|
memset(&portal, 0, sizeof(struct spdk_iscsi_portal));
|
||||||
|
portal.group = &pg;
|
||||||
|
portal.host = IP1;
|
||||||
|
portal.port = "3260";
|
||||||
|
|
||||||
|
/* connection initialization */
|
||||||
|
memset(&conn, 0, sizeof(struct spdk_iscsi_conn));
|
||||||
|
conn.portal = &portal;
|
||||||
|
|
||||||
|
/* initiator group initialization */
|
||||||
|
memset(&ig1, 0, sizeof(struct spdk_iscsi_init_grp));
|
||||||
|
ig1.tag = 1;
|
||||||
|
TAILQ_INIT(&ig1.initiator_head);
|
||||||
|
TAILQ_INIT(&ig1.netmask_head);
|
||||||
|
|
||||||
|
ig1.ninitiators = 1;
|
||||||
|
iname1.name = NULL;
|
||||||
|
TAILQ_INSERT_TAIL(&ig1.initiator_head, &iname1, tailq);
|
||||||
|
|
||||||
|
ig1.nnetmasks = 1;
|
||||||
|
imask1.mask = NULL;
|
||||||
|
TAILQ_INSERT_TAIL(&ig1.netmask_head, &imask1, tailq);
|
||||||
|
|
||||||
|
memset(&ig2, 0, sizeof(struct spdk_iscsi_init_grp));
|
||||||
|
ig2.tag = 2;
|
||||||
|
TAILQ_INIT(&ig2.initiator_head);
|
||||||
|
TAILQ_INIT(&ig2.netmask_head);
|
||||||
|
|
||||||
|
ig2.ninitiators = 1;
|
||||||
|
iname2.name = NULL;
|
||||||
|
TAILQ_INSERT_TAIL(&ig2.initiator_head, &iname2, tailq);
|
||||||
|
|
||||||
|
ig2.nnetmasks = 1;
|
||||||
|
imask2.mask = NULL;
|
||||||
|
TAILQ_INSERT_TAIL(&ig2.netmask_head, &imask2, tailq);
|
||||||
|
|
||||||
|
iqn = IQN1;
|
||||||
|
addr = IP1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 1:
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +-------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | denied | - | - | - | denied |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = NO_IQN1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 2:
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +-------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | allowed | allowed | - | - | allowed |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN1;
|
||||||
|
imask1.mask = IP1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 3:
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +-------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | allowed | denied | denied | - | denied |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN1;
|
||||||
|
imask1.mask = IP2;
|
||||||
|
iname2.name = NO_IQN1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 4:
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +-------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
* | allowed | denied | allowed | allowed | allowed |
|
||||||
|
* +-------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN1;
|
||||||
|
imask1.mask = IP2;
|
||||||
|
iname2.name = IQN1;
|
||||||
|
imask2.mask = IP1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 5:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | allowed | denied | allowed | denied | denied |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN1;
|
||||||
|
imask1.mask = IP2;
|
||||||
|
iname2.name = IQN1;
|
||||||
|
imask2.mask = IP2;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 6:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | allowed | denied | not found | - | denied |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN1;
|
||||||
|
imask1.mask = IP2;
|
||||||
|
iname2.name = IQN2;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 7:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | not found | - | denied | - | denied |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN2;
|
||||||
|
iname2.name = NO_IQN1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 8:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | not found | - | allowed | allowed | allowed |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN2;
|
||||||
|
iname2.name = IQN1;
|
||||||
|
imask2.mask = IP1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 9:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | not found | - | allowed | denied | denied |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN2;
|
||||||
|
iname2.name = IQN1;
|
||||||
|
imask2.mask = IP2;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* case 10:
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | IG1 | IG2 | |
|
||||||
|
* +---------------------------------------------+ |
|
||||||
|
* | name | addr | name | addr | result |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
* | not found | - | not found | - | denied |
|
||||||
|
* +---------------------------------------------+---------+
|
||||||
|
*/
|
||||||
|
iname1.name = IQN2;
|
||||||
|
iname2.name = IQN2;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_access(&conn, &tgtnode, iqn, addr);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
allow_iscsi_name_multi_maps_case(void)
|
||||||
|
{
|
||||||
|
struct spdk_iscsi_tgt_node tgtnode;
|
||||||
|
struct spdk_iscsi_init_grp ig;
|
||||||
|
struct spdk_iscsi_initiator_name iname;
|
||||||
|
char *iqn;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
/* target initialization */
|
||||||
|
memset(&tgtnode, 0, sizeof(struct spdk_iscsi_tgt_node));
|
||||||
|
tgtnode.maxmap = 2;
|
||||||
|
|
||||||
|
tgtnode.map[0].ig = &ig;
|
||||||
|
tgtnode.map[1].ig = &ig;
|
||||||
|
|
||||||
|
/* initiator group initialization */
|
||||||
|
memset(&ig, 0, sizeof(struct spdk_iscsi_init_grp));
|
||||||
|
TAILQ_INIT(&ig.initiator_head);
|
||||||
|
|
||||||
|
ig.ninitiators = 1;
|
||||||
|
iname.name = NULL;
|
||||||
|
TAILQ_INSERT_TAIL(&ig.initiator_head, &iname, tailq);
|
||||||
|
|
||||||
|
/* test for IG1 <-> PG1, PG2 case */
|
||||||
|
iqn = IQN1;
|
||||||
|
|
||||||
|
iname.name = IQN1;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn);
|
||||||
|
CU_ASSERT(result == true);
|
||||||
|
|
||||||
|
iname.name = IQN2;
|
||||||
|
|
||||||
|
result = spdk_iscsi_tgt_node_allow_iscsi_name(&tgtnode, iqn);
|
||||||
|
CU_ASSERT(result == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@ -316,6 +606,10 @@ main(int argc, char **argv)
|
|||||||
|| CU_add_test(suite, "node access allowed case", node_access_allowed) == NULL
|
|| CU_add_test(suite, "node access allowed case", node_access_allowed) == NULL
|
||||||
|| CU_add_test(suite, "node access denied case (empty netmask)",
|
|| CU_add_test(suite, "node access denied case (empty netmask)",
|
||||||
node_access_denied_by_empty_netmask) == NULL
|
node_access_denied_by_empty_netmask) == NULL
|
||||||
|
|| CU_add_test(suite, "node access multiple initiator groups cases",
|
||||||
|
node_access_multi_initiator_groups_cases) == NULL
|
||||||
|
|| CU_add_test(suite, "allow iscsi name case",
|
||||||
|
allow_iscsi_name_multi_maps_case) == NULL
|
||||||
) {
|
) {
|
||||||
CU_cleanup_registry();
|
CU_cleanup_registry();
|
||||||
return CU_get_error();
|
return CU_get_error();
|
||||||
|
Loading…
Reference in New Issue
Block a user