mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
r3898: Work towards local/server DCOM support, start working
on Simple example server side implementation
(This used to be commit 98afb504d9
)
This commit is contained in:
parent
a4de8cd6a5
commit
cc368fa69e
@ -90,7 +90,7 @@ static NTSTATUS $name\__op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_C
|
||||
{
|
||||
uint16 opnum = dce_call->pkt.u.request.opnum;
|
||||
struct GUID ipid = dce_call->pkt.u.request.object.object;
|
||||
struct dcom_interface_p *iface = dcom_get_iface_p(&ipid);
|
||||
struct dcom_interface_p *iface = dcom_get_local_iface_p(&ipid);
|
||||
const struct dcom_$name\_vtable *vtable = iface->vtable;
|
||||
|
||||
dce_call->fault_code = 0;
|
||||
|
39
source4/lib/dcom/classes/simple.c
Normal file
39
source4/lib/dcom/classes/simple.c
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Simple class
|
||||
Copyright (C) 2004 Jelmer Vernooij <jelmer@samba.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/dcom/common/dcom.h"
|
||||
|
||||
static struct dcom_IClassFactory_vtable simple_classobject;
|
||||
|
||||
NTSTATUS dcom_simple_init(void)
|
||||
{
|
||||
struct GUID iid;
|
||||
struct dcom_class simple_class = {
|
||||
"Samba.Simple",
|
||||
};
|
||||
|
||||
GUID_from_string(DCERPC_IUNKNOWN_UUID, &iid);
|
||||
|
||||
simple_class.class_object = dcom_new_local_ifacep(NULL, dcom_interface_by_iid(&iid), &simple_classobject, NULL);
|
||||
|
||||
GUID_from_string("5e9ddec7-5767-11cf-beab-00aa006c3606", &simple_class.clsid);
|
||||
return dcom_register_class(&simple_class);
|
||||
}
|
@ -26,6 +26,7 @@
|
||||
struct IUnknown_AddRef;
|
||||
struct IUnknown_Release;
|
||||
struct IUnknown_QueryInterface;
|
||||
struct dcom_interface_p;
|
||||
|
||||
struct dcom_context
|
||||
{
|
||||
@ -53,7 +54,9 @@ struct dcom_class
|
||||
{
|
||||
const char *prog_id;
|
||||
struct GUID clsid;
|
||||
void (*get_class_object) (struct GUID *iid, void **vtable);
|
||||
|
||||
/* IUnknown */
|
||||
struct dcom_interface_p *class_object;
|
||||
};
|
||||
|
||||
struct dcom_interface
|
||||
|
@ -143,15 +143,15 @@ WERROR dcom_ping(struct dcom_context *ctx)
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
WERROR dcom_create_object(struct dcom_context *ctx, struct GUID *clsid, const char *server, int num_ifaces, struct GUID *iid, struct dcom_interface_p ***ip, WERROR *results)
|
||||
static WERROR dcom_create_object_remote(struct dcom_context *ctx, struct GUID *clsid, const char *server, int num_ifaces, struct GUID *iid, struct dcom_interface_p ***ip, WERROR *results)
|
||||
{
|
||||
uint16 protseq[] = DCOM_NEGOTIATED_PROTOCOLS;
|
||||
struct dcerpc_pipe *p;
|
||||
struct dcom_object_exporter *m;
|
||||
NTSTATUS status;
|
||||
struct RemoteActivation r;
|
||||
struct DUALSTRINGARRAY dualstring;
|
||||
int i;
|
||||
struct dcom_object_exporter *m;
|
||||
struct dcerpc_pipe *p;
|
||||
NTSTATUS status;
|
||||
uint16 protseq[] = DCOM_NEGOTIATED_PROTOCOLS;
|
||||
|
||||
status = dcom_connect_host(ctx, &p, server);
|
||||
if (NT_STATUS_IS_ERR(status)) {
|
||||
@ -207,7 +207,60 @@ WERROR dcom_create_object(struct dcom_context *ctx, struct GUID *clsid, const ch
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
WERROR dcom_get_class_object(struct dcom_context *ctx, struct GUID *clsid, const char *server, struct GUID *iid, struct dcom_interface_p **ip)
|
||||
WERROR dcom_create_object(struct dcom_context *ctx, struct GUID *clsid, const char *server, int num_ifaces, struct GUID *iid, struct dcom_interface_p ***ip, WERROR *results)
|
||||
{
|
||||
struct dcom_interface_p *factory, *iunk;
|
||||
struct QueryInterface qr;
|
||||
struct Release rr;
|
||||
struct CreateInstance cr;
|
||||
WERROR error;
|
||||
int i;
|
||||
NTSTATUS status;
|
||||
|
||||
if (server != NULL) {
|
||||
return dcom_create_object_remote(ctx, clsid, server, num_ifaces, iid, ip, results);
|
||||
}
|
||||
|
||||
/* Obtain class object */
|
||||
error = dcom_get_class_object(ctx, clsid, server, iid, &factory);
|
||||
if (!W_ERROR_IS_OK(error)) {
|
||||
DEBUG(3, ("Unable to obtain class object for %s\n", GUID_string(NULL, clsid)));
|
||||
return error;
|
||||
}
|
||||
|
||||
dcom_OBJREF_from_ifacep(ctx, &cr.in.pUnknown->obj, factory);
|
||||
|
||||
GUID_from_string(DCERPC_ICLASSFACTORY_UUID, cr.in.iid);
|
||||
|
||||
/* Run IClassFactory::CreateInstance() */
|
||||
status = dcom_IClassFactory_CreateInstance(factory, ctx, &cr);
|
||||
if (NT_STATUS_IS_ERR(status)) {
|
||||
DEBUG(3, ("Error while calling IClassFactory::CreateInstance : %s\n", nt_errstr(status)));
|
||||
return ntstatus_to_werror(status);
|
||||
}
|
||||
|
||||
/* Release class object */
|
||||
status = dcom_IUnknown_Release(factory, ctx, &rr);
|
||||
if (NT_STATUS_IS_ERR(status)) {
|
||||
DEBUG(3, ("Error freeing class factory: %s\n", nt_errstr(status)));
|
||||
return ntstatus_to_werror(status);
|
||||
}
|
||||
|
||||
/* Do one or more QueryInterface calls */
|
||||
for (i = 0; i < num_ifaces; i++) {
|
||||
qr.in.iid = &iid[i];
|
||||
status = dcom_IUnknown_QueryInterface(iunk, ctx, &qr);
|
||||
if (NT_STATUS_IS_ERR(status)) {
|
||||
DEBUG(4, ("Error obtaining interface %s : %s\n", GUID_string(NULL, &iid[i]), nt_errstr(status)));
|
||||
return ntstatus_to_werror(status);
|
||||
}
|
||||
results[i] = qr.out.result;
|
||||
}
|
||||
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
WERROR dcom_get_class_object_remote(struct dcom_context *ctx, struct GUID *clsid, const char *server, struct GUID *iid, struct dcom_interface_p **ip)
|
||||
{
|
||||
struct dcom_object_exporter *m;
|
||||
struct RemoteActivation r;
|
||||
@ -258,6 +311,36 @@ WERROR dcom_get_class_object(struct dcom_context *ctx, struct GUID *clsid, const
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
WERROR dcom_get_class_object(struct dcom_context *ctx, struct GUID *clsid, const char *server, struct GUID *iid, struct dcom_interface_p **ip)
|
||||
{
|
||||
const struct dcom_class *c;
|
||||
struct QueryInterface qi;
|
||||
NTSTATUS status;
|
||||
|
||||
if (server != NULL) {
|
||||
return dcom_get_class_object_remote(ctx, clsid, server, iid, ip);
|
||||
}
|
||||
|
||||
c = dcom_class_by_clsid(clsid);
|
||||
if (!c) {
|
||||
/* FIXME: Better error code.. */
|
||||
return WERR_DEST_NOT_FOUND;
|
||||
}
|
||||
|
||||
qi.in.iid = iid;
|
||||
|
||||
status = dcom_IUnknown_QueryInterface(c->class_object, ctx, &qi );
|
||||
if (NT_STATUS_IS_ERR(status)) {
|
||||
return ntstatus_to_werror(status);
|
||||
}
|
||||
|
||||
if (!W_ERROR_IS_OK(qi.out.result)) { return qi.out.result; }
|
||||
|
||||
dcom_ifacep_from_OBJREF(ctx, ip, &qi.out.data->obj);
|
||||
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
NTSTATUS dcom_get_pipe (struct dcom_interface_p *iface, struct dcerpc_pipe **p)
|
||||
{
|
||||
struct dcerpc_binding binding;
|
||||
@ -326,6 +409,12 @@ struct dcom_object *dcom_object_by_oid(struct dcom_object_exporter *ox, HYPER_T
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
NTSTATUS dcom_OBJREF_from_ifacep(struct dcom_context *ctx, struct OBJREF *o, struct dcom_interface_p *_p)
|
||||
{
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NTSTATUS dcom_ifacep_from_OBJREF(struct dcom_context *ctx, struct dcom_interface_p **_p, struct OBJREF *o)
|
||||
{
|
||||
struct dcom_interface_p *p = talloc_p(ctx, struct dcom_interface_p);
|
||||
@ -363,7 +452,7 @@ NTSTATUS dcom_ifacep_from_OBJREF(struct dcom_context *ctx, struct dcom_interface
|
||||
p->ipid = o->u_objref.u_handler.std.ipid;
|
||||
p->object = dcom_object_by_oid(p->ox, o->u_objref.u_standard.std.oid);
|
||||
p->ox->resolver_address = o->u_objref.u_handler.saResAddr;
|
||||
p->vtable = dcom_vtable_by_clsid(&o->u_objref.u_handler.clsid);
|
||||
/*FIXME p->vtable = dcom_vtable_by_clsid(&o->u_objref.u_handler.clsid);*/
|
||||
/* FIXME: Do the custom unmarshaling call */
|
||||
|
||||
*_p = p;
|
||||
@ -371,7 +460,6 @@ NTSTATUS dcom_ifacep_from_OBJREF(struct dcom_context *ctx, struct dcom_interface
|
||||
|
||||
case OBJREF_CUSTOM:
|
||||
{
|
||||
const struct dcom_interface *imarshal = dcom_vtable_by_clsid(&o->u_objref.u_custom.clsid);
|
||||
p->vtable = NULL;
|
||||
|
||||
/* FIXME: Do the actual custom unmarshaling call */
|
||||
@ -437,3 +525,25 @@ NTSTATUS dcom_ifacep_from_OBJREF(struct dcom_context *ctx, struct dcom_interface
|
||||
|
||||
return NT_STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
HYPER_T dcom_get_current_oxid(void)
|
||||
{
|
||||
return getpid();
|
||||
}
|
||||
|
||||
struct dcom_interface_p *dcom_new_local_ifacep(struct dcom_context *ctx, const struct dcom_interface *iface, void *vtable, struct dcom_object *object)
|
||||
{
|
||||
struct dcom_interface_p *ip = talloc_p(ctx, struct dcom_interface_p);
|
||||
|
||||
ip->ctx = ctx;
|
||||
ip->interface = iface;
|
||||
ip->vtable = vtable;
|
||||
uuid_generate_random(&ip->ipid);
|
||||
ip->object = object;
|
||||
ip->objref_flags = 0;
|
||||
ip->orpc_flags = 0;
|
||||
ip->ox = NULL;
|
||||
ip->private_references = 1;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
@ -22,9 +22,13 @@
|
||||
|
||||
#include "includes.h"
|
||||
|
||||
|
||||
struct dcom_interface_p *dcom_get_iface_p(struct GUID *ipid)
|
||||
struct dcom_interface_p *dcom_get_local_iface_p(struct GUID *ipid)
|
||||
{
|
||||
/* FIXME */
|
||||
/* FIXME: Call the local ROT and do a
|
||||
* rot_get_interface_pointer call */
|
||||
|
||||
/* FIXME: Perhaps have a local (thread-local) table with
|
||||
* local DCOM objects so that not every DCOM call requires a lookup
|
||||
* to the ROT? */
|
||||
return NULL;
|
||||
}
|
||||
|
@ -46,14 +46,15 @@ const struct dcom_interface *dcom_interface_by_iid(const struct GUID *iid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void *dcom_vtable_by_clsid(const struct GUID *clsid)
|
||||
const struct dcom_class *dcom_class_by_clsid(const struct GUID *clsid)
|
||||
{
|
||||
struct class_list *c = classes;
|
||||
|
||||
while(c) {
|
||||
|
||||
if (uuid_equal(clsid, &c->class.clsid))
|
||||
if (uuid_equal(clsid, &c->class.clsid)) {
|
||||
return &c->class;
|
||||
}
|
||||
|
||||
c = c->next;
|
||||
}
|
||||
|
@ -7,6 +7,12 @@ INIT_OBJ_FILES = \
|
||||
lib/dcom/common/rot.o
|
||||
REQUIRED_SUBSYSTEMS = DCOM_PROXY_DCOM RPC_NDR_REMACT \
|
||||
RPC_NDR_OXIDRESOLVER
|
||||
|
||||
[MODULE::DCOM_SIMPLE]
|
||||
SUBSYSTEM = LIBDCOM
|
||||
INIT_FUNCTION = dcom_simple_init
|
||||
INIT_OBJ_FILES = \
|
||||
lib/dcom/classes/simple.o
|
||||
#
|
||||
# End SUBSYSTEM LIBDCOM
|
||||
################################################
|
||||
|
@ -46,9 +46,9 @@ BOOL SMBencrypt(const char *passwd, const uint8_t *c8, uint8_t p24[24])
|
||||
|
||||
#ifdef DEBUG_PASSWORD
|
||||
DEBUG(100,("SMBencrypt: lm#, challenge, response\n"));
|
||||
dump_data(100, (char *)p21, 16);
|
||||
dump_data(100, (const char *)c8, 8);
|
||||
dump_data(100, (char *)p24, 24);
|
||||
dump_data(100, p21, 16);
|
||||
dump_data(100, c8, 8);
|
||||
dump_data(100, p24, 24);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
@ -196,9 +196,9 @@ void SMBNTencrypt(const char *passwd, uint8_t *c8, uint8_t *p24)
|
||||
|
||||
#ifdef DEBUG_PASSWORD
|
||||
DEBUG(100,("SMBNTencrypt: nt#, challenge, response\n"));
|
||||
dump_data(100, (char *)p21, 16);
|
||||
dump_data(100, (char *)c8, 8);
|
||||
dump_data(100, (char *)p24, 24);
|
||||
dump_data(100, p21, 16);
|
||||
dump_data(100, c8, 8);
|
||||
dump_data(100, p24, 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ SUBSYSTEM = DCERPC
|
||||
INIT_OBJ_FILES = \
|
||||
rpc_server/dcom/oxidresolver.o \
|
||||
rpc_server/dcom/rot.o \
|
||||
rpc_server/dcom/rodb.o \
|
||||
rpc_server/dcom/remact.o \
|
||||
librpc/gen_ndr/ndr_dcom_d.o
|
||||
REQUIRED_SUBSYSTEMS = \
|
||||
|
39
source4/rpc_server/dcom/rodb.c
Normal file
39
source4/rpc_server/dcom/rodb.c
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Running objects database
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2004
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "rpc_server/dcerpc_server.h"
|
||||
#include "librpc/gen_ndr/ndr_rot.h"
|
||||
#include "rpc_server/common/common.h"
|
||||
|
||||
struct tdb_wrap *openrodb(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct tdb_wrap *wrap;
|
||||
char *rodb_name = NULL;
|
||||
|
||||
asprintf(&rodb_name, "%s/rot.tdb", lp_lockdir());
|
||||
wrap = tdb_wrap_open(mem_ctx, rodb_name, 0, 0, O_RDWR|O_CREAT, 0600);
|
||||
SAFE_FREE(rodb_name);
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
@ -25,14 +25,15 @@
|
||||
#include "librpc/gen_ndr/ndr_rot.h"
|
||||
#include "rpc_server/common/common.h"
|
||||
|
||||
|
||||
/*
|
||||
rot_add
|
||||
*/
|
||||
static WERROR rot_add(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_add *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +43,9 @@ static WERROR rot_add(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
static WERROR rot_remove(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_remove *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +55,9 @@ static WERROR rot_remove(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx
|
||||
static WERROR rot_is_listed(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_is_listed *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +67,9 @@ static WERROR rot_is_listed(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_
|
||||
static WERROR rot_get_interface_pointer(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_get_interface_pointer *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -72,7 +79,9 @@ static WERROR rot_get_interface_pointer(struct dcesrv_call_state *dce_call, TALL
|
||||
static WERROR rot_set_modification_time(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_set_modification_time *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +91,9 @@ static WERROR rot_set_modification_time(struct dcesrv_call_state *dce_call, TALL
|
||||
static WERROR rot_get_modification_time(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_get_modification_time *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
@ -92,7 +103,9 @@ static WERROR rot_get_modification_time(struct dcesrv_call_state *dce_call, TALL
|
||||
static WERROR rot_enum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
|
||||
struct rot_enum *r)
|
||||
{
|
||||
struct tdb_wrap *db = openrodb(mem_ctx);
|
||||
DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
|
||||
talloc_destroy(db);
|
||||
}
|
||||
|
||||
|
||||
|
@ -47,15 +47,13 @@ BOOL torture_dcom_simple(void)
|
||||
|
||||
mem_ctx = talloc_init("torture_dcom_simple");
|
||||
|
||||
dcom_init(&ctx, lp_parm_string(-1, "torture", "userdomain"),
|
||||
lp_parm_string(-1, "torture", "username"),
|
||||
lp_parm_string(-1, "torture", "password"));
|
||||
torture_dcom_init(&ctx);
|
||||
|
||||
GUID_from_string(DCERPC_ISTREAM_UUID, &IID[0]);
|
||||
GUID_from_string(DCERPC_IUNKNOWN_UUID, &IID[1]);
|
||||
GUID_from_string(CLSID_SIMPLE, &clsid);
|
||||
error = dcom_create_object(ctx, &clsid,
|
||||
lp_parm_string(-1, "torture", "binding"), 2, IID,
|
||||
lp_parm_string(-1, "torture", "dcomhost"), 2, IID,
|
||||
&interfaces,
|
||||
results);
|
||||
|
||||
|
@ -141,6 +141,15 @@ BOOL torture_close_connection(struct smbcli_state *c)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* initialise a DCOM context */
|
||||
NTSTATUS torture_dcom_init(struct dcom_context **ctx)
|
||||
{
|
||||
dcom_init(ctx, lp_parm_string(-1, "torture", "userdomain"),
|
||||
lp_parm_string(-1, "torture", "username"),
|
||||
lp_parm_string(-1, "torture", "password"));
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* open a rpc connection to the chosen binding string */
|
||||
NTSTATUS torture_rpc_connection(struct dcerpc_pipe **p,
|
||||
|
Loading…
Reference in New Issue
Block a user