Compare commits
6 Commits
Release32-
...
Release32-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80cb27c795 | ||
|
|
cb73eadf01 | ||
|
|
69ba3f8789 | ||
|
|
c380bbe0e3 | ||
|
|
da74e877c5 | ||
|
|
a300873b3f |
@@ -105,6 +105,7 @@ struct dns_server_info {
|
||||
int ttl;
|
||||
int ttl_range;
|
||||
SSL *ssl;
|
||||
int ssl_write_len;
|
||||
SSL_CTX *ssl_ctx;
|
||||
SSL_SESSION *ssl_session;
|
||||
char skip_check_cert;
|
||||
@@ -861,6 +862,7 @@ static int _dns_client_server_add(char *server_ip, char *server_host, int port,
|
||||
|
||||
SSL_CTX_set_options(server_info->ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
SSL_CTX_set_session_cache_mode(server_info->ssl_ctx, SSL_SESS_CACHE_CLIENT);
|
||||
SSL_CTX_sess_set_cache_size(server_info->ssl_ctx, 64);
|
||||
if (_dns_client_set_trusted_cert(server_info->ssl_ctx) != 0) {
|
||||
tlog(TLOG_WARN, "disable check certificate for %s.", server_info->ip);
|
||||
server_info->skip_check_cert = 1;
|
||||
@@ -932,9 +934,12 @@ static void _dns_client_close_socket(struct dns_server_info *server_info)
|
||||
|
||||
if (server_info->ssl) {
|
||||
/* Shutdown ssl */
|
||||
SSL_shutdown(server_info->ssl);
|
||||
if (server_info->status == DNS_SERVER_STATUS_CONNECTED) {
|
||||
SSL_shutdown(server_info->ssl);
|
||||
}
|
||||
SSL_free(server_info->ssl);
|
||||
server_info->ssl = NULL;
|
||||
server_info->ssl_write_len = -1;
|
||||
}
|
||||
|
||||
/* remove fd from epoll */
|
||||
@@ -949,6 +954,36 @@ static void _dns_client_close_socket(struct dns_server_info *server_info)
|
||||
tlog(TLOG_DEBUG, "server %s closed.", server_info->ip);
|
||||
}
|
||||
|
||||
static void _dns_client_shutdown_socket(struct dns_server_info *server_info)
|
||||
{
|
||||
if (server_info->fd <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (server_info->type) {
|
||||
case DNS_SERVER_UDP:
|
||||
return;
|
||||
break;
|
||||
case DNS_SERVER_TCP:
|
||||
if (server_info->fd > 0) {
|
||||
shutdown(server_info->fd, SHUT_RDWR);
|
||||
}
|
||||
break;
|
||||
case DNS_SERVER_TLS:
|
||||
case DNS_SERVER_HTTPS:
|
||||
if (server_info->ssl) {
|
||||
/* Shutdown ssl */
|
||||
if (server_info->status == DNS_SERVER_STATUS_CONNECTED) {
|
||||
SSL_shutdown(server_info->ssl);
|
||||
}
|
||||
shutdown(server_info->fd, SHUT_RDWR);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _dns_client_server_close(struct dns_server_info *server_info)
|
||||
{
|
||||
/* stop ping task */
|
||||
@@ -1627,6 +1662,7 @@ static int _DNS_client_create_socket_tls(struct dns_server_info *server_info, ch
|
||||
|
||||
server_info->fd = fd;
|
||||
server_info->ssl = ssl;
|
||||
server_info->ssl_write_len = -1;
|
||||
server_info->status = DNS_SERVER_STATUS_CONNECTING;
|
||||
|
||||
tlog(TLOG_DEBUG, "tls server %s connecting.\n", server_info->ip);
|
||||
@@ -1750,9 +1786,9 @@ static int _dns_client_socket_ssl_send(SSL *ssl, const void *buf, int num)
|
||||
ssl_ret = SSL_get_error(ssl, ret);
|
||||
switch (ssl_ret) {
|
||||
case SSL_ERROR_NONE:
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return 0;
|
||||
break;
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
case SSL_ERROR_WANT_READ:
|
||||
errno = EAGAIN;
|
||||
ret = -1;
|
||||
@@ -1765,7 +1801,8 @@ static int _dns_client_socket_ssl_send(SSL *ssl, const void *buf, int num)
|
||||
ssl_err = ERR_get_error();
|
||||
int ssl_reason = ERR_GET_REASON(ssl_err);
|
||||
if (ssl_reason == SSL_R_UNINITIALIZED || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN ||
|
||||
ssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT) {
|
||||
ssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT ||
|
||||
ssl_reason == SSL_R_BAD_WRITE_RETRY) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
@@ -1828,11 +1865,15 @@ static int _dns_client_socket_ssl_recv(SSL *ssl, void *buf, int num)
|
||||
return 0;
|
||||
}
|
||||
|
||||
tlog(TLOG_ERROR, "SSL read fail error no: %s(%ld)\n", ERR_reason_error_string(ssl_err), ssl_err);
|
||||
tlog(TLOG_ERROR, "SSL read fail error no: %s(%lx)\n", ERR_reason_error_string(ssl_err), ssl_err);
|
||||
errno = EFAULT;
|
||||
ret = -1;
|
||||
break;
|
||||
case SSL_ERROR_SYSCALL:
|
||||
if (errno == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (errno != ECONNRESET) {
|
||||
tlog(TLOG_INFO, "SSL syscall failed, %s ", strerror(errno));
|
||||
}
|
||||
@@ -1854,7 +1895,18 @@ static int _dns_client_socket_send(struct dns_server_info *server_info)
|
||||
} else if (server_info->type == DNS_SERVER_TCP) {
|
||||
return send(server_info->fd, server_info->send_buff.data, server_info->send_buff.len, MSG_NOSIGNAL);
|
||||
} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS) {
|
||||
return _dns_client_socket_ssl_send(server_info->ssl, server_info->send_buff.data, server_info->send_buff.len);
|
||||
int write_len = server_info->send_buff.len;
|
||||
if (server_info->ssl_write_len > 0) {
|
||||
write_len = server_info->ssl_write_len;
|
||||
server_info->ssl_write_len = -1;
|
||||
}
|
||||
int ret = _dns_client_socket_ssl_send(server_info->ssl, server_info->send_buff.data, write_len);
|
||||
if (ret != 0) {
|
||||
if (errno == EAGAIN) {
|
||||
server_info->ssl_write_len = write_len;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -2115,7 +2167,8 @@ static int _dns_client_tls_matchName(const char *host, const char *pattern, int
|
||||
return match;
|
||||
}
|
||||
|
||||
static int _dns_client_tls_get_cert_CN(X509 *cert, char *cn, int max_cn_len) {
|
||||
static int _dns_client_tls_get_cert_CN(X509 *cert, char *cn, int max_cn_len)
|
||||
{
|
||||
X509_NAME *cert_name = NULL;
|
||||
|
||||
cert_name = X509_get_subject_name(cert);
|
||||
@@ -2477,7 +2530,7 @@ static int _dns_client_send_tls(struct dns_server_info *server_info, void *packe
|
||||
/* save data to buffer, and retry when EPOLLOUT is available */
|
||||
return _dns_client_send_data_to_buffer(server_info, inpacket, len);
|
||||
} else if (server_info->ssl && errno != ENOMEM) {
|
||||
SSL_set_shutdown(server_info->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
||||
SSL_shutdown(server_info->ssl);
|
||||
}
|
||||
return -1;
|
||||
} else if (send_len < len) {
|
||||
@@ -2596,21 +2649,18 @@ static int _dns_client_send_packet(struct dns_query_struct *query, void *packet,
|
||||
tlog(TLOG_DEBUG, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err),
|
||||
server_info->type);
|
||||
_dns_client_close_socket(server_info);
|
||||
} else if (send_err != ENOMEM) {
|
||||
tlog(TLOG_ERROR, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err),
|
||||
server_info->type);
|
||||
} else {
|
||||
tlog(TLOG_DEBUG, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err),
|
||||
server_info->type);
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now - 5 > server_info->last_recv) {
|
||||
server_info->recv_buff.len = 0;
|
||||
server_info->send_buff.len = 0;
|
||||
tlog(TLOG_DEBUG, "server %s not response, retry.", server_info->ip);
|
||||
_dns_client_close_socket(server_info);
|
||||
}
|
||||
atomic_dec(&query->dns_request_sent);
|
||||
continue;
|
||||
}
|
||||
|
||||
tlog(TLOG_DEBUG, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err),
|
||||
server_info->type);
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now - 5 > server_info->last_recv || send_err != ENOMEM) {
|
||||
_dns_client_shutdown_socket(server_info);
|
||||
}
|
||||
|
||||
atomic_dec(&query->dns_request_sent);
|
||||
continue;
|
||||
}
|
||||
@@ -2882,7 +2932,6 @@ static void _dns_client_add_pending_servers(void)
|
||||
|
||||
/* if both A, AAAA has query result, select fastest IP address */
|
||||
if (pending->has_v4 && pending->has_v6) {
|
||||
|
||||
if (pending->ping_time_v4 <= pending->ping_time_v6 && pending->ipv4[0]) {
|
||||
dnsserver_ip = pending->ipv4;
|
||||
} else {
|
||||
|
||||
165
src/dns_server.c
165
src/dns_server.c
@@ -1631,15 +1631,106 @@ static int _dns_server_passthrough_rule_check(struct dns_request *request, char
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _dns_server_get_answer(struct dns_request *request, struct dns_packet *packet)
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int ttl = 0;
|
||||
struct dns_rrs *rrs = NULL;
|
||||
int rr_count = 0;
|
||||
char name[DNS_MAX_CNAME_LEN] = {0};
|
||||
|
||||
for (j = 1; j < DNS_RRS_END; j++) {
|
||||
rrs = dns_get_rrs_start(packet, j, &rr_count);
|
||||
for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {
|
||||
switch (rrs->type) {
|
||||
case DNS_T_A: {
|
||||
unsigned char addr[4];
|
||||
char name[DNS_MAX_CNAME_LEN] = {0};
|
||||
|
||||
if (request->qtype != DNS_T_A) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get A result */
|
||||
dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
|
||||
memcpy(request->ipv4_addr, addr, DNS_RR_A_LEN);
|
||||
request->ttl_v4 = _dns_server_get_conf_ttl(ttl);
|
||||
request->has_ipv4 = 1;
|
||||
request->rcode = packet->head.rcode;
|
||||
} break;
|
||||
case DNS_T_AAAA: {
|
||||
unsigned char addr[16];
|
||||
char name[DNS_MAX_CNAME_LEN] = {0};
|
||||
|
||||
if (request->qtype != DNS_T_AAAA) {
|
||||
/* ignore non-matched query type */
|
||||
continue;
|
||||
}
|
||||
dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
|
||||
memcpy(request->ipv6_addr, addr, DNS_RR_AAAA_LEN);
|
||||
request->ttl_v6 = _dns_server_get_conf_ttl(ttl);
|
||||
request->has_ipv6 = 1;
|
||||
request->rcode = packet->head.rcode;
|
||||
} break;
|
||||
case DNS_T_NS: {
|
||||
char cname[DNS_MAX_CNAME_LEN];
|
||||
dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);
|
||||
tlog(TLOG_DEBUG, "NS: %s ttl:%d cname: %s\n", name, ttl, cname);
|
||||
} break;
|
||||
case DNS_T_CNAME: {
|
||||
char cname[DNS_MAX_CNAME_LEN];
|
||||
dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);
|
||||
tlog(TLOG_DEBUG, "name:%s ttl: %d cname: %s\n", name, ttl, cname);
|
||||
safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN);
|
||||
request->ttl_cname = ttl;
|
||||
request->has_cname = 1;
|
||||
} break;
|
||||
case DNS_T_SOA: {
|
||||
request->has_soa = 1;
|
||||
request->rcode = packet->head.rcode;
|
||||
dns_get_SOA(rrs, name, 128, &ttl, &request->soa);
|
||||
tlog(TLOG_DEBUG,
|
||||
"domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, expire: "
|
||||
"%d, minimum: %d",
|
||||
request->domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial,
|
||||
request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);
|
||||
if (atomic_inc_return(&request->soa_num) >= (dns_server_num() / 2)) {
|
||||
_dns_server_request_complete(request);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
tlog(TLOG_DEBUG, "%s, qtype: %d", name, rrs->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _dns_server_reply_passthrouth(struct dns_request *request, struct dns_packet *packet,
|
||||
unsigned char *inpacket, int inpacket_len)
|
||||
{
|
||||
int ttl;
|
||||
char name[DNS_MAX_CNAME_LEN] = {0};
|
||||
int rr_count;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
struct dns_rrs *rrs = NULL;
|
||||
struct dns_ipset_rule *ipset_rule = NULL;
|
||||
struct dns_rule_flags *rule_flags = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (atomic_inc_return(&request->notified) != 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (request->result_callback) {
|
||||
_dns_server_get_answer(request, packet);
|
||||
_dns_result_callback(request);
|
||||
}
|
||||
|
||||
if (request->conn == NULL) {
|
||||
return 0;
|
||||
}
|
||||
@@ -1648,6 +1739,74 @@ static int _dns_server_reply_passthrouth(struct dns_request *request, struct dns
|
||||
dns_server_update_reply_packet_id(request, inpacket, inpacket_len);
|
||||
ret = _dns_reply_inpacket(request, inpacket, inpacket_len);
|
||||
|
||||
if (packet->head.rcode != DNS_RC_NOERROR && packet->head.rcode != DNS_RC_NXDOMAIN) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_IPSET) == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check ipset rule */
|
||||
rule_flags = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
|
||||
if (rule_flags) {
|
||||
if (rule_flags->flags & DOMAIN_FLAG_IPSET_IGNORE) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ipset_rule = request->domain_rule.rules[DOMAIN_RULE_IPSET];
|
||||
if (ipset_rule == NULL) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (j = 1; j < DNS_RRS_END; j++) {
|
||||
rrs = dns_get_rrs_start(packet, j, &rr_count);
|
||||
for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {
|
||||
switch (rrs->type) {
|
||||
case DNS_T_A: {
|
||||
unsigned char addr[4];
|
||||
if (request->qtype != DNS_T_A) {
|
||||
/* ignore non-matched query type */
|
||||
if (request->dualstack_selection == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* get A result */
|
||||
dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
|
||||
|
||||
/* add IPV4 to ipset */
|
||||
ipset_add(ipset_rule->ipsetname, addr, DNS_RR_A_LEN, request->ttl_v4 * 2);
|
||||
|
||||
tlog(TLOG_DEBUG, "IPSET-MATCH-PASSTHROUTH: domain: %s, ipset: %s, IP: %d.%d.%d.%d",
|
||||
request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3]);
|
||||
} break;
|
||||
case DNS_T_AAAA: {
|
||||
unsigned char addr[16];
|
||||
if (request->qtype != DNS_T_AAAA) {
|
||||
/* ignore non-matched query type */
|
||||
break;
|
||||
}
|
||||
dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
|
||||
|
||||
/* add IPV6 to ipset */
|
||||
if (request->has_ipv6) {
|
||||
if (request->has_ipv4) {
|
||||
ipset_add(ipset_rule->ipsetname, addr, DNS_RR_A_LEN, request->ttl_v4 * 2);
|
||||
}
|
||||
ipset_add(ipset_rule->ipsetname, addr, DNS_RR_AAAA_LEN, request->ttl_v6 * 2);
|
||||
}
|
||||
|
||||
tlog(TLOG_DEBUG, "IPSET-MATCH-PASSTHROUTH: domain: %s, ipset: %s, IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
|
||||
request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8],
|
||||
addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1673,7 +1832,6 @@ static int dns_server_resolve_callback(char *domain, dns_result_type rtype, unsi
|
||||
|
||||
return _dns_server_reply_passthrouth(request, packet, inpacket, inpacket_len);
|
||||
}
|
||||
|
||||
_dns_server_process_answer(request, domain, packet, result_flag);
|
||||
return 0;
|
||||
} else if (rtype == DNS_QUERY_ERR) {
|
||||
@@ -2045,7 +2203,7 @@ static int _dns_server_process_cache(struct dns_request *request)
|
||||
if (dns_cache_get_ttl(dns_cache_A) == 0) {
|
||||
_dns_server_prefetch_request(request->domain, request->qtype);
|
||||
}
|
||||
ret = _dns_server_reply_SOA(DNS_RC_NOERROR, request);
|
||||
ret = _dns_server_reply_SOA(DNS_RC_NOERROR, request);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
@@ -2439,6 +2597,7 @@ int dns_server_query(char *domain, int qtype, dns_result_callback callback, void
|
||||
return ret;
|
||||
errout:
|
||||
if (request) {
|
||||
_dns_server_request_set_callback(request, NULL, NULL);
|
||||
_dns_server_request_release(request);
|
||||
}
|
||||
|
||||
@@ -3073,7 +3232,7 @@ static int _dns_create_socket(const char *host_ip, int type)
|
||||
setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));
|
||||
|
||||
if (bind(fd, gai->ai_addr, gai->ai_addrlen) != 0) {
|
||||
tlog(TLOG_ERROR, "bind service failed, %s\n", strerror(errno));
|
||||
tlog(TLOG_ERROR, "bind service %s failed, %s\n", host_ip, strerror(errno));
|
||||
goto errout;
|
||||
}
|
||||
|
||||
|
||||
@@ -439,6 +439,7 @@ int main(int argc, char *argv[])
|
||||
goto errout;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
if (dns_server_load_conf(config_file) != 0) {
|
||||
fprintf(stderr, "load config failed.\n");
|
||||
goto errout;
|
||||
@@ -451,7 +452,6 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
signal(SIGINT, _sig_exit);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
atexit(_smartdns_exit);
|
||||
|
||||
return _smartdns_run();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* tinylog
|
||||
* Copyright (C) 2018-2019 Nick Peng <pymumu@gmail.com>
|
||||
* Copyright (C) 2018-2020 Nick Peng <pymumu@gmail.com>
|
||||
* https://github.com/pymumu/tinylog
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
@@ -1377,7 +1377,7 @@ static void *_tlog_work(void *arg)
|
||||
log = _tlog_wait_log_locked(log);
|
||||
if (log == NULL) {
|
||||
pthread_mutex_unlock(&tlog.lock);
|
||||
if (errno != ETIMEDOUT) {
|
||||
if (errno != ETIMEDOUT && tlog.run) {
|
||||
sleep(1);
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* tinylog
|
||||
* Copyright (C) 2018-2019 Ruilin Peng (Nick) <pymumu@gmail.com>
|
||||
* Copyright (C) 2018-2020 Ruilin Peng (Nick) <pymumu@gmail.com>
|
||||
* https://github.com/pymumu/tinylog
|
||||
*/
|
||||
|
||||
@@ -196,6 +196,7 @@ public:
|
||||
Buffer LogStream(tlog_level level, const char *file, int line, const char *func, void *userptr) {
|
||||
return Buffer(new Stream, [=](Stream *st) {
|
||||
tlog_ext(level, file, line, func, userptr, "%s", st->str().c_str());
|
||||
delete st;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -215,6 +216,7 @@ public:
|
||||
Buffer Out(tlog_log *log) {
|
||||
return Buffer(new Stream, [=](Stream *st) {
|
||||
tlog_printf(log, "%s", st->str().c_str());
|
||||
delete st;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user