* [Refactor] Moved Reverse Proxy Code
Moved the reverse proxy code from reqs.c into it's own files (reverse_proxy.c). The code in reqs.c is way too complicated, so I want to move unrelated code into their own files to simplify the main concepts in reqs.c.
This commit is contained in:
parent
808bdbd1e9
commit
4c58663041
@ -1,4 +1,4 @@
|
|||||||
dnl $Id: configure.ac,v 2.69 2004-08-24 18:43:17 rjkaes Exp $
|
dnl $Id: configure.ac,v 2.70 2005-08-16 04:03:18 rjkaes Exp $
|
||||||
|
|
||||||
dnl Devlopers, please strive to achieve this order:
|
dnl Devlopers, please strive to achieve this order:
|
||||||
dnl
|
dnl
|
||||||
@ -130,6 +130,7 @@ TP_ARG_ENABLE(reverse,
|
|||||||
[Enable reverse proxying (default is NO)]
|
[Enable reverse proxying (default is NO)]
|
||||||
no)
|
no)
|
||||||
if test x"$reverse_enabled" = x"yes"; then
|
if test x"$reverse_enabled" = x"yes"; then
|
||||||
|
ADDITIONAL_OBJECTS="$ADDITIONAL_OBJECTS reverse_proxy.o"
|
||||||
AC_DEFINE(REVERSE_SUPPORT)
|
AC_DEFINE(REVERSE_SUPPORT)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# $Id: Makefile.am,v 1.22 2005-07-12 17:39:40 rjkaes Exp $
|
# $Id: Makefile.am,v 1.23 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 Robert James Kaes (rjkaes@users.sourceforge.net)
|
# Copyright (C) 2000 Robert James Kaes (rjkaes@users.sourceforge.net)
|
||||||
#
|
#
|
||||||
@ -40,7 +40,8 @@ tinyproxy_SOURCES = \
|
|||||||
utils.c utils.h \
|
utils.c utils.h \
|
||||||
vector.c vector.h
|
vector.c vector.h
|
||||||
|
|
||||||
EXTRA_tinyproxy_SOURCES = filter.c filter.h grammar.h
|
EXTRA_tinyproxy_SOURCES = filter.c filter.h \
|
||||||
|
reverse_proxy.c reverse_proxy.h
|
||||||
tinyproxy_DEPENDENCIES = @ADDITIONAL_OBJECTS@
|
tinyproxy_DEPENDENCIES = @ADDITIONAL_OBJECTS@
|
||||||
tinyproxy_LDADD = @ADDITIONAL_OBJECTS@
|
tinyproxy_LDADD = @ADDITIONAL_OBJECTS@
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* $Id: conffile.c,v 1.5 2005-08-15 03:54:31 rjkaes Exp $
|
/* $Id: conffile.c,v 1.6 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
*
|
*
|
||||||
* Parses the configuration file and sets up the config_s structure for
|
* Parses the configuration file and sets up the config_s structure for
|
||||||
* use by the application. This file replaces the old grammar.y and
|
* use by the application. This file replaces the old grammar.y and
|
||||||
@ -30,6 +30,7 @@
|
|||||||
#include "htmlerror.h"
|
#include "htmlerror.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "reqs.h"
|
#include "reqs.h"
|
||||||
|
#include "reverse_proxy.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The configuration directives are defined in the structure below. Each
|
* The configuration directives are defined in the structure below. Each
|
||||||
|
159
src/reqs.c
159
src/reqs.c
@ -1,4 +1,4 @@
|
|||||||
/* $Id: reqs.c,v 1.120 2005-08-15 03:54:31 rjkaes Exp $
|
/* $Id: reqs.c,v 1.121 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
*
|
*
|
||||||
* This is where all the work in tinyproxy is actually done. Incoming
|
* This is where all the work in tinyproxy is actually done. Incoming
|
||||||
* connections have a new child created for them. The child then
|
* connections have a new child created for them. The child then
|
||||||
@ -6,7 +6,7 @@
|
|||||||
* and then relays the bytes between the two.
|
* and then relays the bytes between the two.
|
||||||
*
|
*
|
||||||
* Copyright (C) 1998 Steven Young
|
* Copyright (C) 1998 Steven Young
|
||||||
* Copyright (C) 1999-2004 Robert James Kaes (rjkaes@users.sourceforge.net)
|
* Copyright (C) 1999-2005 Robert James Kaes (rjkaes@users.sourceforge.net)
|
||||||
* Copyright (C) 2000 Chris Lightfoot (chris@ex-parrot.com)
|
* Copyright (C) 2000 Chris Lightfoot (chris@ex-parrot.com)
|
||||||
* Copyright (C) 2002 Petr Lampa (lampa@fit.vutbr.cz)
|
* Copyright (C) 2002 Petr Lampa (lampa@fit.vutbr.cz)
|
||||||
*
|
*
|
||||||
@ -39,6 +39,7 @@
|
|||||||
#include "text.h"
|
#include "text.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
#include "reverse_proxy.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Maximum length of a HTTP line
|
* Maximum length of a HTTP line
|
||||||
@ -497,75 +498,6 @@ upstream_get(char *host)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef REVERSE_SUPPORT
|
|
||||||
/*
|
|
||||||
* Add entry to the reversepath list
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
reversepath_add(const char *path, const char *url)
|
|
||||||
{
|
|
||||||
struct reversepath *reverse;
|
|
||||||
|
|
||||||
if (url == NULL) {
|
|
||||||
log_message(LOG_WARNING,
|
|
||||||
"Illegal reverse proxy rule: missing url");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strstr(url, "://")) {
|
|
||||||
log_message(LOG_WARNING,
|
|
||||||
"Skipping reverse proxy rule: '%s' is not a valid url",
|
|
||||||
url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path && *path != '/') {
|
|
||||||
log_message(LOG_WARNING,
|
|
||||||
"Skipping reverse proxy rule: path '%s' doesn't start with a /",
|
|
||||||
path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(reverse = safemalloc(sizeof(struct reversepath)))) {
|
|
||||||
log_message(LOG_ERR,
|
|
||||||
"Unable to allocate memory in reversepath_add()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path)
|
|
||||||
reverse->path = safestrdup("/");
|
|
||||||
else
|
|
||||||
reverse->path = safestrdup(path);
|
|
||||||
|
|
||||||
reverse->url = safestrdup(url);
|
|
||||||
|
|
||||||
reverse->next = config.reversepath_list;
|
|
||||||
config.reversepath_list = reverse;
|
|
||||||
|
|
||||||
log_message(LOG_INFO,
|
|
||||||
"Added reverse proxy rule: %s -> %s", reverse->path,
|
|
||||||
reverse->url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if a request url is in the reversepath list
|
|
||||||
*/
|
|
||||||
static struct reversepath *
|
|
||||||
reversepath_get(char *url)
|
|
||||||
{
|
|
||||||
struct reversepath *reverse = config.reversepath_list;
|
|
||||||
|
|
||||||
while (reverse) {
|
|
||||||
if (strstr(url, reverse->path) == url)
|
|
||||||
return reverse;
|
|
||||||
|
|
||||||
reverse = reverse->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a connection for HTTP connections.
|
* Create a connection for HTTP connections.
|
||||||
*/
|
*/
|
||||||
@ -616,16 +548,7 @@ process_request(struct conn_s *connptr, hashmap_t hashofheaders)
|
|||||||
{
|
{
|
||||||
char *url;
|
char *url;
|
||||||
struct request_s *request;
|
struct request_s *request;
|
||||||
|
|
||||||
#ifdef REVERSE_SUPPORT
|
|
||||||
char *rewrite_url = NULL;
|
|
||||||
char *cookie = NULL;
|
|
||||||
char *cookieval;
|
|
||||||
struct reversepath *reverse;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
size_t request_len;
|
size_t request_len;
|
||||||
|
|
||||||
/* NULL out all the fields so frees don't cause segfaults. */
|
/* NULL out all the fields so frees don't cause segfaults. */
|
||||||
@ -698,72 +621,24 @@ process_request(struct conn_s *connptr, hashmap_t hashofheaders)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#ifdef REVERSE_SUPPORT
|
#ifdef REVERSE_SUPPORT
|
||||||
/*
|
|
||||||
* Reverse proxy URL rewriting.
|
|
||||||
*/
|
|
||||||
if (config.reversepath_list != NULL) {
|
if (config.reversepath_list != NULL) {
|
||||||
/* Reverse requests always start with a slash */
|
/*
|
||||||
if (*url == '/') {
|
* Rewrite the URL based on the reverse path. After calling
|
||||||
/* First try locating the reverse mapping by request url */
|
* reverse_rewrite_url "url" can be freed since we either
|
||||||
reverse = reversepath_get(url);
|
* have the newly rewritten URL, or something failed and
|
||||||
if (reverse) {
|
* we'll be closing anyway.
|
||||||
rewrite_url = safemalloc(strlen(url) +
|
*/
|
||||||
strlen(reverse->url) +
|
char *reverse_url;
|
||||||
1);
|
|
||||||
strcpy(rewrite_url, reverse->url);
|
|
||||||
strcat(rewrite_url,
|
|
||||||
url + strlen(reverse->path));
|
|
||||||
} else if (config.reversemagic
|
|
||||||
&& hashmap_entry_by_key(hashofheaders,
|
|
||||||
"cookie",
|
|
||||||
(void **)&cookie) >
|
|
||||||
0) {
|
|
||||||
|
|
||||||
/* No match - try the magical tracking cookie next */
|
|
||||||
if ((cookieval =
|
|
||||||
strstr(cookie, REVERSE_COOKIE "="))
|
|
||||||
&& (reverse =
|
|
||||||
reversepath_get(cookieval +
|
|
||||||
strlen(REVERSE_COOKIE) +
|
|
||||||
1))) {
|
|
||||||
|
|
||||||
rewrite_url = safemalloc(strlen(url) +
|
|
||||||
strlen
|
|
||||||
(reverse->
|
|
||||||
url) + 1);
|
|
||||||
strcpy(rewrite_url, reverse->url);
|
|
||||||
strcat(rewrite_url, url + 1);
|
|
||||||
|
|
||||||
log_message(LOG_INFO,
|
|
||||||
"Magical tracking cookie says: %s",
|
|
||||||
reverse->path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forward proxy support off and no reverse path match found */
|
|
||||||
if (config.reverseonly && !rewrite_url) {
|
|
||||||
log_message(LOG_ERR, "Bad request");
|
|
||||||
indicate_http_error(connptr, 400, "Bad Request",
|
|
||||||
"detail",
|
|
||||||
"Request has an invalid URL", "url",
|
|
||||||
url, NULL);
|
|
||||||
|
|
||||||
safefree(url);
|
|
||||||
free_request_struct(request);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_message(LOG_CONN, "Rewriting URL: %s -> %s",
|
|
||||||
url, rewrite_url);
|
|
||||||
|
|
||||||
|
reverse_url = reverse_rewrite_url(connptr, hashofheaders, url);
|
||||||
safefree(url);
|
safefree(url);
|
||||||
url = rewrite_url;
|
|
||||||
|
|
||||||
/* Store reverse path so that the magical tracking cookie can be set */
|
if (!reverse_url) {
|
||||||
if (config.reversemagic)
|
free_request_struct(request);
|
||||||
connptr->reversepath = safestrdup(reverse->path);
|
return NULL;
|
||||||
|
} else {
|
||||||
|
url = reverse_url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* $Id: reqs.h,v 1.6 2005-07-12 17:39:44 rjkaes Exp $
|
/* $Id: reqs.h,v 1.7 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
*
|
*
|
||||||
* See 'reqs.c' for a detailed description.
|
* See 'reqs.c' for a detailed description.
|
||||||
*
|
*
|
||||||
@ -22,6 +22,5 @@
|
|||||||
extern void handle_connection(int fd);
|
extern void handle_connection(int fd);
|
||||||
extern void add_connect_port_allowed(int port);
|
extern void add_connect_port_allowed(int port);
|
||||||
extern void upstream_add(const char *host, int port, const char *domain);
|
extern void upstream_add(const char *host, int port, const char *domain);
|
||||||
extern void reversepath_add(const char *path, const char *url);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
154
src/reverse_proxy.c
Normal file
154
src/reverse_proxy.c
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/* $Id: reverse_proxy.c,v 1.1 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
|
*
|
||||||
|
* Allow tinyproxy to be used as a reverse proxy.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999-2005 Robert James Kaes (rjkaes@users.sourceforge.net)
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tinyproxy.h"
|
||||||
|
#include "reverse_proxy.h"
|
||||||
|
|
||||||
|
#include "conns.h"
|
||||||
|
#include "heap.h"
|
||||||
|
#include "htmlerror.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add entry to the reversepath list
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
reversepath_add(const char *path, const char *url)
|
||||||
|
{
|
||||||
|
struct reversepath *reverse;
|
||||||
|
|
||||||
|
if (url == NULL) {
|
||||||
|
log_message(LOG_WARNING,
|
||||||
|
"Illegal reverse proxy rule: missing url");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strstr(url, "://")) {
|
||||||
|
log_message(LOG_WARNING,
|
||||||
|
"Skipping reverse proxy rule: '%s' is not a valid url",
|
||||||
|
url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path && *path != '/') {
|
||||||
|
log_message(LOG_WARNING,
|
||||||
|
"Skipping reverse proxy rule: path '%s' doesn't start with a /",
|
||||||
|
path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(reverse = safemalloc(sizeof(struct reversepath)))) {
|
||||||
|
log_message(LOG_ERR,
|
||||||
|
"Unable to allocate memory in reversepath_add()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path)
|
||||||
|
reverse->path = safestrdup("/");
|
||||||
|
else
|
||||||
|
reverse->path = safestrdup(path);
|
||||||
|
|
||||||
|
reverse->url = safestrdup(url);
|
||||||
|
|
||||||
|
reverse->next = config.reversepath_list;
|
||||||
|
config.reversepath_list = reverse;
|
||||||
|
|
||||||
|
log_message(LOG_INFO,
|
||||||
|
"Added reverse proxy rule: %s -> %s", reverse->path,
|
||||||
|
reverse->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if a request url is in the reversepath list
|
||||||
|
*/
|
||||||
|
struct reversepath *
|
||||||
|
reversepath_get(char *url)
|
||||||
|
{
|
||||||
|
struct reversepath *reverse = config.reversepath_list;
|
||||||
|
|
||||||
|
while (reverse) {
|
||||||
|
if (strstr(url, reverse->path) == url)
|
||||||
|
return reverse;
|
||||||
|
|
||||||
|
reverse = reverse->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rewrite the URL for reverse proxying.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
reverse_rewrite_url(struct conn_s *connptr, hashmap_t hashofheaders, char *url)
|
||||||
|
{
|
||||||
|
char *rewrite_url = NULL;
|
||||||
|
char *cookie = NULL;
|
||||||
|
char *cookieval;
|
||||||
|
struct reversepath *reverse;
|
||||||
|
|
||||||
|
/* Reverse requests always start with a slash */
|
||||||
|
if (*url == '/') {
|
||||||
|
/* First try locating the reverse mapping by request url */
|
||||||
|
reverse = reversepath_get(url);
|
||||||
|
if (reverse) {
|
||||||
|
rewrite_url = safemalloc(strlen(url) +
|
||||||
|
strlen(reverse->url) + 1);
|
||||||
|
strcpy(rewrite_url, reverse->url);
|
||||||
|
strcat(rewrite_url, url + strlen(reverse->path));
|
||||||
|
} else if (config.reversemagic
|
||||||
|
&& hashmap_entry_by_key(hashofheaders,
|
||||||
|
"cookie",
|
||||||
|
(void **)&cookie) > 0) {
|
||||||
|
|
||||||
|
/* No match - try the magical tracking cookie next */
|
||||||
|
if ((cookieval = strstr(cookie, REVERSE_COOKIE "="))
|
||||||
|
&& (reverse =
|
||||||
|
reversepath_get(cookieval +
|
||||||
|
strlen(REVERSE_COOKIE) + 1))) {
|
||||||
|
|
||||||
|
rewrite_url = safemalloc(strlen(url) +
|
||||||
|
strlen
|
||||||
|
(reverse->url) + 1);
|
||||||
|
strcpy(rewrite_url, reverse->url);
|
||||||
|
strcat(rewrite_url, url + 1);
|
||||||
|
|
||||||
|
log_message(LOG_INFO,
|
||||||
|
"Magical tracking cookie says: %s",
|
||||||
|
reverse->path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forward proxy support off and no reverse path match found */
|
||||||
|
if (config.reverseonly && !rewrite_url) {
|
||||||
|
log_message(LOG_ERR, "Bad request");
|
||||||
|
indicate_http_error(connptr, 400, "Bad Request",
|
||||||
|
"detail",
|
||||||
|
"Request has an invalid URL", "url",
|
||||||
|
url, NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message(LOG_CONN, "Rewriting URL: %s -> %s", url, rewrite_url);
|
||||||
|
|
||||||
|
/* Store reverse path so that the magical tracking cookie can be set */
|
||||||
|
if (config.reversemagic)
|
||||||
|
connptr->reversepath = safestrdup(reverse->path);
|
||||||
|
|
||||||
|
return rewrite_url;
|
||||||
|
}
|
36
src/reverse_proxy.h
Normal file
36
src/reverse_proxy.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* $Id: reverse_proxy.h,v 1.1 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
|
*
|
||||||
|
* See 'reverse_proxy.c' for a detailed description.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Robert James Kaes (rjkaes@users.sourceforge.net)
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TINYPROXY_REVERSE_PROXY_H
|
||||||
|
#define TINYPROXY_REVERSE_PROXY_H
|
||||||
|
|
||||||
|
#include "conns.h"
|
||||||
|
|
||||||
|
struct reversepath {
|
||||||
|
struct reversepath *next;
|
||||||
|
char *path;
|
||||||
|
char *url;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define REVERSE_COOKIE "yummy_magical_cookie"
|
||||||
|
|
||||||
|
extern void reversepath_add(const char *path, const char *url);
|
||||||
|
extern struct reversepath *reversepath_get(char *url);
|
||||||
|
extern char *reverse_rewrite_url(struct conn_s *connptr,
|
||||||
|
hashmap_t hashofheaders, char *url);
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,4 @@
|
|||||||
/* $Id: tinyproxy.h,v 1.47 2005-08-15 03:54:31 rjkaes Exp $
|
/* $Id: tinyproxy.h,v 1.48 2005-08-16 04:03:19 rjkaes Exp $
|
||||||
*
|
*
|
||||||
* See 'tinyproxy.c' for a detailed description.
|
* See 'tinyproxy.c' for a detailed description.
|
||||||
*
|
*
|
||||||
@ -38,16 +38,6 @@ struct upstream {
|
|||||||
in_addr_t ip, mask;
|
in_addr_t ip, mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef REVERSE_SUPPORT
|
|
||||||
struct reversepath {
|
|
||||||
struct reversepath *next;
|
|
||||||
char *path;
|
|
||||||
char *url;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define REVERSE_COOKIE "yummy_magical_cookie"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hold all the configuration time information.
|
* Hold all the configuration time information.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user