mirror of
https://github.com/samba-team/samba.git
synced 2025-01-26 10:04:02 +03:00
add tdb backup function separation and winbind idmap upgrade code form
pre-2.2.4 tdb database format. tx volker for your work on this (This used to be commit 2bdbeb9e97a59ecd16f74fbb04ab5ca57b28a757)
This commit is contained in:
parent
52e4b4d5ab
commit
52826c034e
@ -159,7 +159,7 @@ MODULES = $(VFS_MODULES) $(PDB_MODULES) $(RPC_MODULES) $(IDMAP_MODULES) $(CHARSE
|
||||
######################################################################
|
||||
|
||||
TDBBASE_OBJ = tdb/tdb.o tdb/spinlock.o
|
||||
TDB_OBJ = $(TDBBASE_OBJ) tdb/tdbutil.o
|
||||
TDB_OBJ = $(TDBBASE_OBJ) tdb/tdbutil.o tdb/tdbback.o
|
||||
|
||||
SMBLDAP_OBJ = @SMBLDAP@
|
||||
|
||||
@ -613,7 +613,7 @@ WINBIND_NSS_PICOBJS = $(WINBIND_NSS_OBJ:.o=.po)
|
||||
POPT_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
|
||||
popt/popthelp.o popt/poptparse.o
|
||||
|
||||
TDBBACKUP_OBJ = tdb/tdbbackup.o $(TDBBASE_OBJ)
|
||||
TDBBACKUP_OBJ = tdb/tdbbackup.o tdb/tdbback.o $(TDBBASE_OBJ)
|
||||
|
||||
NTLM_AUTH_OBJ = utils/ntlm_auth.o $(LIBSAMBA_OBJ) $(POPT_LIB_OBJ)
|
||||
|
||||
|
@ -842,6 +842,9 @@ int main(int argc, char **argv)
|
||||
|
||||
/* Winbind daemon initialisation */
|
||||
|
||||
if (!winbindd_upgrade_idmap())
|
||||
return 1;
|
||||
|
||||
if (!idmap_init())
|
||||
return 1;
|
||||
|
||||
|
@ -574,3 +574,209 @@ DOM_SID *rid_to_talloced_sid(struct winbindd_domain *domain,
|
||||
return sid;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
For idmap conversion: convert one record to new format
|
||||
Ancient versions (eg 2.2.3a) of winbindd_idmap.tdb mapped DOMAINNAME/rid
|
||||
instead of the SID.
|
||||
*****************************************************************************/
|
||||
static int convert_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *state)
|
||||
{
|
||||
struct winbindd_domain *domain;
|
||||
char *p;
|
||||
DOM_SID sid;
|
||||
uint32 rid;
|
||||
fstring keystr;
|
||||
fstring dom_name;
|
||||
TDB_DATA key2;
|
||||
BOOL *failed = (BOOL *)state;
|
||||
|
||||
DEBUG(10,("Converting %s\n", key.dptr));
|
||||
|
||||
p = strchr(key.dptr, '/');
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
*p = 0;
|
||||
fstrcpy(dom_name, key.dptr);
|
||||
*p++ = '/';
|
||||
|
||||
domain = find_domain_from_name(dom_name);
|
||||
if (domain == NULL) {
|
||||
/* We must delete the old record. */
|
||||
DEBUG(0,("Unable to find domain %s\n", dom_name ));
|
||||
DEBUG(0,("deleting record %s\n", key.dptr ));
|
||||
|
||||
if (tdb_delete(tdb, key) != 0) {
|
||||
DEBUG(0, ("Unable to delete record %s\n", key.dptr));
|
||||
*failed = True;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
rid = atoi(p);
|
||||
|
||||
sid_copy(&sid, &domain->sid);
|
||||
sid_append_rid(&sid, rid);
|
||||
|
||||
sid_to_string(keystr, &sid);
|
||||
key2.dptr = keystr;
|
||||
key2.dsize = strlen(keystr) + 1;
|
||||
|
||||
if (tdb_store(tdb, key2, data, TDB_INSERT) != 0) {
|
||||
DEBUG(0,("Unable to add record %s\n", key2.dptr ));
|
||||
*failed = True;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tdb_store(tdb, data, key2, TDB_REPLACE) != 0) {
|
||||
DEBUG(0,("Unable to update record %s\n", data.dptr ));
|
||||
*failed = True;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tdb_delete(tdb, key) != 0) {
|
||||
DEBUG(0,("Unable to delete record %s\n", key.dptr ));
|
||||
*failed = True;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* These definitions are from sam/idmap_tdb.c. Replicated here just
|
||||
out of laziness.... :-( */
|
||||
|
||||
/* High water mark keys */
|
||||
#define HWM_GROUP "GROUP HWM"
|
||||
#define HWM_USER "USER HWM"
|
||||
|
||||
/* idmap version determines auto-conversion */
|
||||
#define IDMAP_VERSION 2
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
Convert the idmap database from an older version.
|
||||
*****************************************************************************/
|
||||
|
||||
static BOOL idmap_convert(const char *idmap_name)
|
||||
{
|
||||
int32 vers;
|
||||
BOOL bigendianheader;
|
||||
BOOL failed = False;
|
||||
TDB_CONTEXT *idmap_tdb;
|
||||
|
||||
if (!(idmap_tdb = tdb_open_log(idmap_name, 0,
|
||||
TDB_DEFAULT, O_RDWR,
|
||||
0600))) {
|
||||
DEBUG(0, ("idmap_convert: Unable to open idmap database\n"));
|
||||
return False;
|
||||
}
|
||||
|
||||
bigendianheader = (idmap_tdb->flags & TDB_BIGENDIAN) ? True : False;
|
||||
|
||||
vers = tdb_fetch_int32(idmap_tdb, "IDMAP_VERSION");
|
||||
|
||||
if (((vers == -1) && bigendianheader) || (IREV(vers) == IDMAP_VERSION)) {
|
||||
/* Arrggghh ! Bytereversed or old big-endian - make order independent ! */
|
||||
/*
|
||||
* high and low records were created on a
|
||||
* big endian machine and will need byte-reversing.
|
||||
*/
|
||||
|
||||
int32 wm;
|
||||
|
||||
wm = tdb_fetch_int32(idmap_tdb, HWM_USER);
|
||||
|
||||
if (wm != -1) {
|
||||
wm = IREV(wm);
|
||||
} else {
|
||||
wm = server_state.uid_low;
|
||||
}
|
||||
|
||||
if (tdb_store_int32(idmap_tdb, HWM_USER, wm) == -1) {
|
||||
DEBUG(0, ("idmap_convert: Unable to byteswap user hwm in idmap database\n"));
|
||||
tdb_close(idmap_tdb);
|
||||
return False;
|
||||
}
|
||||
|
||||
wm = tdb_fetch_int32(idmap_tdb, HWM_GROUP);
|
||||
if (wm != -1) {
|
||||
wm = IREV(wm);
|
||||
} else {
|
||||
wm = server_state.gid_low;
|
||||
}
|
||||
|
||||
if (tdb_store_int32(idmap_tdb, HWM_GROUP, wm) == -1) {
|
||||
DEBUG(0, ("idmap_convert: Unable to byteswap group hwm in idmap database\n"));
|
||||
tdb_close(idmap_tdb);
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
/* the old format stored as DOMAIN/rid - now we store the SID direct */
|
||||
tdb_traverse(idmap_tdb, convert_fn, &failed);
|
||||
|
||||
if (failed) {
|
||||
DEBUG(0, ("Problem during conversion\n"));
|
||||
tdb_close(idmap_tdb);
|
||||
return False;
|
||||
}
|
||||
|
||||
if (tdb_store_int32(idmap_tdb, "IDMAP_VERSION", IDMAP_VERSION) == -1) {
|
||||
DEBUG(0, ("idmap_convert: Unable to dtore idmap version in databse\n"));
|
||||
tdb_close(idmap_tdb);
|
||||
return False;
|
||||
}
|
||||
|
||||
tdb_close(idmap_tdb);
|
||||
return True;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
Convert the idmap database from an older version if necessary
|
||||
*****************************************************************************/
|
||||
|
||||
BOOL winbindd_upgrade_idmap(void)
|
||||
{
|
||||
pstring idmap_name;
|
||||
pstring backup_name;
|
||||
SMB_STRUCT_STAT stbuf;
|
||||
TDB_CONTEXT *idmap_tdb;
|
||||
|
||||
pstrcpy(idmap_name, lock_path("winbindd_idmap.tdb"));
|
||||
|
||||
if (!file_exist(idmap_name, &stbuf)) {
|
||||
/* nothing to convert return */
|
||||
return True;
|
||||
}
|
||||
|
||||
if (!(idmap_tdb = tdb_open_log(idmap_name, 0,
|
||||
TDB_DEFAULT, O_RDWR,
|
||||
0600))) {
|
||||
DEBUG(0, ("idmap_convert: Unable to open idmap database\n"));
|
||||
return False;
|
||||
}
|
||||
|
||||
if (tdb_fetch_int32(idmap_tdb, "IDMAP_VERSION") == IDMAP_VERSION) {
|
||||
/* nothing to convert return */
|
||||
tdb_close(idmap_tdb);
|
||||
return True;
|
||||
}
|
||||
|
||||
/* backup_tdb expects the tdb not to be open */
|
||||
tdb_close(idmap_tdb);
|
||||
|
||||
DEBUG(0, ("Upgrading winbindd_idmap.tdb from an old version\n"));
|
||||
|
||||
pstrcpy(backup_name, idmap_name);
|
||||
pstrcat(backup_name, ".bak");
|
||||
|
||||
if (backup_tdb(idmap_name, backup_name) != 0) {
|
||||
DEBUG(0, ("Could not backup idmap database\n"));
|
||||
return False;
|
||||
}
|
||||
|
||||
return idmap_convert(idmap_name);
|
||||
}
|
||||
|
201
source3/tdb/tdbback.c
Normal file
201
source3/tdb/tdbback.c
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
low level tdb backup and restore utility
|
||||
Copyright (C) Andrew Tridgell 2002
|
||||
|
||||
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 <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include "tdb.h"
|
||||
|
||||
static int failed;
|
||||
|
||||
char *add_suffix(const char *name, const char *suffix)
|
||||
{
|
||||
char *ret;
|
||||
int len = strlen(name) + strlen(suffix) + 1;
|
||||
ret = malloc(len);
|
||||
if (!ret) {
|
||||
fprintf(stderr,"Out of memory!\n");
|
||||
exit(1);
|
||||
}
|
||||
strncpy(ret, name, len);
|
||||
strncat(ret, suffix, len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
|
||||
{
|
||||
TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state;
|
||||
|
||||
if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) {
|
||||
fprintf(stderr,"Failed to insert into %s\n", tdb_new->name);
|
||||
failed = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
carefully backup a tdb, validating the contents and
|
||||
only doing the backup if its OK
|
||||
this function is also used for restore
|
||||
*/
|
||||
int backup_tdb(const char *old_name, const char *new_name)
|
||||
{
|
||||
TDB_CONTEXT *tdb;
|
||||
TDB_CONTEXT *tdb_new;
|
||||
char *tmp_name;
|
||||
struct stat st;
|
||||
int count1, count2;
|
||||
|
||||
tmp_name = add_suffix(new_name, ".tmp");
|
||||
|
||||
/* stat the old tdb to find its permissions */
|
||||
if (stat(old_name, &st) != 0) {
|
||||
perror(old_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* open the old tdb */
|
||||
tdb = tdb_open(old_name, 0, 0, O_RDWR, 0);
|
||||
if (!tdb) {
|
||||
printf("Failed to open %s\n", old_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* create the new tdb */
|
||||
unlink(tmp_name);
|
||||
tdb_new = tdb_open(tmp_name, tdb->header.hash_size,
|
||||
TDB_DEFAULT, O_RDWR|O_CREAT|O_EXCL,
|
||||
st.st_mode & 0777);
|
||||
if (!tdb_new) {
|
||||
perror(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* lock the old tdb */
|
||||
if (tdb_lockall(tdb) != 0) {
|
||||
fprintf(stderr,"Failed to lock %s\n", old_name);
|
||||
tdb_close(tdb);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
failed = 0;
|
||||
|
||||
/* traverse and copy */
|
||||
count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
|
||||
if (count1 < 0 || failed) {
|
||||
fprintf(stderr,"failed to copy %s\n", old_name);
|
||||
tdb_close(tdb);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* close the old tdb */
|
||||
tdb_close(tdb);
|
||||
|
||||
/* close the new tdb and re-open read-only */
|
||||
tdb_close(tdb_new);
|
||||
tdb_new = tdb_open(tmp_name, 0, TDB_DEFAULT, O_RDONLY, 0);
|
||||
if (!tdb_new) {
|
||||
fprintf(stderr,"failed to reopen %s\n", tmp_name);
|
||||
unlink(tmp_name);
|
||||
perror(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* traverse the new tdb to confirm */
|
||||
count2 = tdb_traverse(tdb_new, test_fn, 0);
|
||||
if (count2 != count1) {
|
||||
fprintf(stderr,"failed to copy %s\n", old_name);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* make sure the new tdb has reached stable storage */
|
||||
fsync(tdb_new->fd);
|
||||
|
||||
/* close the new tdb and rename it to .bak */
|
||||
tdb_close(tdb_new);
|
||||
unlink(new_name);
|
||||
if (rename(tmp_name, new_name) != 0) {
|
||||
perror(new_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(tmp_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
verify a tdb and if it is corrupt then restore from *.bak
|
||||
*/
|
||||
int verify_tdb(const char *fname, const char *bak_name)
|
||||
{
|
||||
TDB_CONTEXT *tdb;
|
||||
int count = -1;
|
||||
|
||||
/* open the tdb */
|
||||
tdb = tdb_open(fname, 0, 0, O_RDONLY, 0);
|
||||
|
||||
/* traverse the tdb, then close it */
|
||||
if (tdb) {
|
||||
count = tdb_traverse(tdb, test_fn, NULL);
|
||||
tdb_close(tdb);
|
||||
}
|
||||
|
||||
/* count is < 0 means an error */
|
||||
if (count < 0) {
|
||||
printf("restoring %s\n", fname);
|
||||
return backup_tdb(bak_name, fname);
|
||||
}
|
||||
|
||||
printf("%s : %d records\n", fname, count);
|
||||
|
||||
return 0;
|
||||
}
|
@ -55,175 +55,7 @@
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include "tdb.h"
|
||||
|
||||
static int failed;
|
||||
|
||||
static char *add_suffix(const char *name, const char *suffix)
|
||||
{
|
||||
char *ret;
|
||||
int len = strlen(name) + strlen(suffix) + 1;
|
||||
ret = malloc(len);
|
||||
if (!ret) {
|
||||
fprintf(stderr,"Out of memory!\n");
|
||||
exit(1);
|
||||
}
|
||||
strncpy(ret, name, len);
|
||||
strncat(ret, suffix, len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
|
||||
{
|
||||
TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state;
|
||||
|
||||
if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) {
|
||||
fprintf(stderr,"Failed to insert into %s\n", tdb_new->name);
|
||||
failed = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
carefully backup a tdb, validating the contents and
|
||||
only doing the backup if its OK
|
||||
this function is also used for restore
|
||||
*/
|
||||
static int backup_tdb(const char *old_name, const char *new_name)
|
||||
{
|
||||
TDB_CONTEXT *tdb;
|
||||
TDB_CONTEXT *tdb_new;
|
||||
char *tmp_name;
|
||||
struct stat st;
|
||||
int count1, count2;
|
||||
|
||||
tmp_name = add_suffix(new_name, ".tmp");
|
||||
|
||||
/* stat the old tdb to find its permissions */
|
||||
if (stat(old_name, &st) != 0) {
|
||||
perror(old_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* open the old tdb */
|
||||
tdb = tdb_open(old_name, 0, 0, O_RDWR, 0);
|
||||
if (!tdb) {
|
||||
printf("Failed to open %s\n", old_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* create the new tdb */
|
||||
unlink(tmp_name);
|
||||
tdb_new = tdb_open(tmp_name, tdb->header.hash_size,
|
||||
TDB_DEFAULT, O_RDWR|O_CREAT|O_EXCL,
|
||||
st.st_mode & 0777);
|
||||
if (!tdb_new) {
|
||||
perror(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* lock the old tdb */
|
||||
if (tdb_lockall(tdb) != 0) {
|
||||
fprintf(stderr,"Failed to lock %s\n", old_name);
|
||||
tdb_close(tdb);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
failed = 0;
|
||||
|
||||
/* traverse and copy */
|
||||
count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
|
||||
if (count1 < 0 || failed) {
|
||||
fprintf(stderr,"failed to copy %s\n", old_name);
|
||||
tdb_close(tdb);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* close the old tdb */
|
||||
tdb_close(tdb);
|
||||
|
||||
/* close the new tdb and re-open read-only */
|
||||
tdb_close(tdb_new);
|
||||
tdb_new = tdb_open(tmp_name, 0, TDB_DEFAULT, O_RDONLY, 0);
|
||||
if (!tdb_new) {
|
||||
fprintf(stderr,"failed to reopen %s\n", tmp_name);
|
||||
unlink(tmp_name);
|
||||
perror(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* traverse the new tdb to confirm */
|
||||
count2 = tdb_traverse(tdb_new, test_fn, 0);
|
||||
if (count2 != count1) {
|
||||
fprintf(stderr,"failed to copy %s\n", old_name);
|
||||
tdb_close(tdb_new);
|
||||
unlink(tmp_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* make sure the new tdb has reached stable storage */
|
||||
fsync(tdb_new->fd);
|
||||
|
||||
/* close the new tdb and rename it to .bak */
|
||||
tdb_close(tdb_new);
|
||||
unlink(new_name);
|
||||
if (rename(tmp_name, new_name) != 0) {
|
||||
perror(new_name);
|
||||
free(tmp_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s : %d records\n", old_name, count1);
|
||||
free(tmp_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
verify a tdb and if it is corrupt then restore from *.bak
|
||||
*/
|
||||
static int verify_tdb(const char *fname, const char *bak_name)
|
||||
{
|
||||
TDB_CONTEXT *tdb;
|
||||
int count = -1;
|
||||
|
||||
/* open the tdb */
|
||||
tdb = tdb_open(fname, 0, 0, O_RDONLY, 0);
|
||||
|
||||
/* traverse the tdb, then close it */
|
||||
if (tdb) {
|
||||
count = tdb_traverse(tdb, test_fn, NULL);
|
||||
tdb_close(tdb);
|
||||
}
|
||||
|
||||
/* count is < 0 means an error */
|
||||
if (count < 0) {
|
||||
printf("restoring %s\n", fname);
|
||||
return backup_tdb(bak_name, fname);
|
||||
}
|
||||
|
||||
printf("%s : %d records\n", fname, count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "tdbback.h"
|
||||
|
||||
/*
|
||||
see if one file is newer than another
|
||||
|
Loading…
x
Reference in New Issue
Block a user