Merge branch '4170-extend-expire-opt-support-into-xfrin-c' into 'main'
Resolve "Extend EXPIRE opt support into xfrin.c" Closes #4170 See merge request isc-projects/bind9!8064
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
6233. [func] Extend client side support for the EDNS EXPIRE option
|
||||
to IXFR and AXFR query types. [GL #4170]
|
||||
|
||||
6232. [bug] Following the introduction of krb5-subdomain-self-rhs
|
||||
and ms-subdomain-self-rhs update rules, removal of
|
||||
nonexistent PTR and SRV records via UPDATE could fail.
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
messages=1
|
||||
records=5
|
||||
bytes=204
|
||||
bytes=223
|
||||
@@ -0,0 +1,3 @@
|
||||
messages=1
|
||||
records=5
|
||||
bytes=215
|
||||
@@ -388,9 +388,9 @@ status=$((status+ret))
|
||||
n=$((n+1))
|
||||
echo_i "checking whether dig calculates IXFR statistics correctly ($n)"
|
||||
ret=0
|
||||
$DIG $DIGOPTS +noedns +stat -b 10.53.0.4 @10.53.0.4 test. ixfr=2 > dig.out1.test$n
|
||||
$DIG $DIGOPTS +expire +nocookie +stat -b 10.53.0.4 @10.53.0.4 test. ixfr=2 > dig.out1.test$n
|
||||
get_dig_xfer_stats dig.out1.test$n > stats.dig
|
||||
diff ixfr-stats.good stats.dig > /dev/null || ret=1
|
||||
diff ixfr-stats-with-expire.good stats.dig > /dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status+ret))
|
||||
|
||||
@@ -400,20 +400,20 @@ status=$((status+ret))
|
||||
|
||||
_wait_for_stats () {
|
||||
get_named_xfer_stats ns4/named.run "$1" test "$2" > "$3"
|
||||
diff ixfr-stats.good "$3" > /dev/null || return 1
|
||||
diff "$4" "$3" > /dev/null || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
n=$((n+1))
|
||||
echo_i "checking whether named calculates incoming IXFR statistics correctly ($n)"
|
||||
ret=0
|
||||
retry_quiet 10 _wait_for_stats 10.53.0.3 "Transfer completed" stats.incoming || ret=1
|
||||
retry_quiet 10 _wait_for_stats 10.53.0.3 "Transfer completed" stats.incoming ixfr-stats-without-expire.good || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status+ret))
|
||||
|
||||
n=$((n+1))
|
||||
echo_i "checking whether named calculates outgoing IXFR statistics correctly ($n)"
|
||||
retry_quiet 10 _wait_for_stats 10.53.0.4 "IXFR ended" stats.outgoing || ret=1
|
||||
retry_quiet 10 _wait_for_stats 10.53.0.4 "IXFR ended" stats.outgoing ixfr-stats-with-expire.good || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status+ret))
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
messages=16
|
||||
records=10003
|
||||
bytes=218227
|
||||
bytes=218403
|
||||
|
||||
@@ -464,7 +464,15 @@ $DIGCMD nil. TXT | grep 'SOA mismatch AXFR' >/dev/null && {
|
||||
}
|
||||
|
||||
n=$((n+1))
|
||||
echo_i "check that we ask for and get a EDNS EXPIRE response ($n)"
|
||||
echo_i "check that we ask for and got a EDNS EXPIRE response when transfering from a secondary ($n)"
|
||||
tmp=0
|
||||
msg="zone edns-expire/IN: zone transfer finished: success, expire=1814[0-4][0-9][0-9]"
|
||||
grep "$msg" ns7/named.run > /dev/null || tmp=1
|
||||
[ "$tmp" -ne 0 ] && echo_i "failed"
|
||||
status=$((status+tmp))
|
||||
|
||||
n=$((n+1))
|
||||
echo_i "check that we ask for and get a EDNS EXPIRE response when refreshing ($n)"
|
||||
# force a refresh query
|
||||
$RNDCCMD 10.53.0.7 refresh edns-expire 2>&1 | sed 's/^/ns7 /' | cat_i
|
||||
sleep 10
|
||||
@@ -541,7 +549,7 @@ tmp=0
|
||||
# Use -b so that we can discern between incoming and outgoing transfers in ns3
|
||||
# logs later on.
|
||||
wait_for_xfer() (
|
||||
$DIG $DIGOPTS +noedns +stat -b 10.53.0.2 @10.53.0.3 xfer-stats. AXFR > dig.out.ns3.test$n
|
||||
$DIG $DIGOPTS +edns +nocookie +noexpire +stat -b 10.53.0.2 @10.53.0.3 xfer-stats. AXFR > dig.out.ns3.test$n
|
||||
grep "; Transfer failed" dig.out.ns3.test$n > /dev/null || return 0
|
||||
return 1
|
||||
)
|
||||
|
||||
@@ -35,6 +35,10 @@ Feature Changes
|
||||
- Make :iscman:`nsupdate` honor the ``-v`` option for SOA queries, that is send
|
||||
the request over TCP, only if the server is specified. :gl:`#1181`
|
||||
|
||||
- Extend client side support for the EDNS EXPIRE option to IXFR and
|
||||
AXFR query types. ``named`` will now be making EDNS queries AXFR
|
||||
and IXFR queries with EDNS options present. :gl:`#4170`
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ typedef isc_result_t (*dns_additionaldatafunc_t)(
|
||||
|
||||
typedef isc_result_t (*dns_digestfunc_t)(void *, isc_region_t *);
|
||||
|
||||
typedef void (*dns_xfrindone_t)(dns_zone_t *, isc_result_t);
|
||||
typedef void (*dns_xfrindone_t)(dns_zone_t *, uint32_t *, isc_result_t);
|
||||
|
||||
typedef void (*dns_updatecallback_t)(void *, isc_result_t, dns_message_t *);
|
||||
|
||||
|
||||
+112
-4
@@ -30,6 +30,7 @@
|
||||
#include <dns/journal.h>
|
||||
#include <dns/log.h>
|
||||
#include <dns/message.h>
|
||||
#include <dns/peer.h>
|
||||
#include <dns/rdataclass.h>
|
||||
#include <dns/rdatalist.h>
|
||||
#include <dns/rdataset.h>
|
||||
@@ -141,7 +142,8 @@ struct dns_xfrin {
|
||||
|
||||
xfrin_state_t state;
|
||||
uint32_t end_serial;
|
||||
bool is_ixfr;
|
||||
uint32_t expireopt;
|
||||
bool edns, is_ixfr, expireoptset;
|
||||
|
||||
unsigned int nmsg; /*%< Number of messages recvd */
|
||||
unsigned int nrecs; /*%< Number of records recvd */
|
||||
@@ -833,7 +835,9 @@ xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg) {
|
||||
dns_journal_destroy(&xfr->ixfr.journal);
|
||||
}
|
||||
if (xfr->done != NULL) {
|
||||
(xfr->done)(xfr->zone, result);
|
||||
(xfr->done)(xfr->zone,
|
||||
xfr->expireoptset ? &xfr->expireopt : NULL,
|
||||
result);
|
||||
xfr->done = NULL;
|
||||
}
|
||||
xfr->shutdown_result = result;
|
||||
@@ -860,6 +864,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
|
||||
.primaryaddr = *primaryaddr,
|
||||
.sourceaddr = *sourceaddr,
|
||||
.firstsoa = DNS_RDATA_INIT,
|
||||
.edns = true,
|
||||
.magic = XFRIN_MAGIC,
|
||||
};
|
||||
|
||||
@@ -1147,6 +1152,38 @@ request_type(dns_xfrin_t *xfr) {
|
||||
}
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid,
|
||||
bool reqexpire) {
|
||||
isc_result_t result;
|
||||
dns_rdataset_t *rdataset = NULL;
|
||||
dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
|
||||
int count = 0;
|
||||
|
||||
/* Set EDNS options if applicable. */
|
||||
if (reqnsid) {
|
||||
INSIST(count < DNS_EDNSOPTIONS);
|
||||
ednsopts[count].code = DNS_OPT_NSID;
|
||||
ednsopts[count].length = 0;
|
||||
ednsopts[count].value = NULL;
|
||||
count++;
|
||||
}
|
||||
if (reqexpire) {
|
||||
INSIST(count < DNS_EDNSOPTIONS);
|
||||
ednsopts[count].code = DNS_OPT_EXPIRE;
|
||||
ednsopts[count].length = 0;
|
||||
ednsopts[count].value = NULL;
|
||||
count++;
|
||||
}
|
||||
result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0,
|
||||
ednsopts, count);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
return (result);
|
||||
}
|
||||
|
||||
return (dns_message_setopt(message, rdataset));
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an *XFR request and send its length prefix.
|
||||
*/
|
||||
@@ -1160,6 +1197,10 @@ xfrin_send_request(dns_xfrin_t *xfr) {
|
||||
dns_name_t *qname = NULL;
|
||||
dns_dbversion_t *ver = NULL;
|
||||
dns_name_t *msgsoaname = NULL;
|
||||
bool edns = xfr->edns;
|
||||
bool reqnsid = xfr->view->requestnsid;
|
||||
bool reqexpire = dns_zone_getrequestexpire(xfr->zone);
|
||||
uint16_t udpsize = dns_view_getudpsize(xfr->view);
|
||||
|
||||
LIBDNS_XFRIN_RECV_SEND_REQUEST(xfr, xfr->info);
|
||||
|
||||
@@ -1198,6 +1239,24 @@ xfrin_send_request(dns_xfrin_t *xfr) {
|
||||
&xfr->ixfr.request_serial));
|
||||
}
|
||||
|
||||
if (edns && xfr->view->peers != NULL) {
|
||||
dns_peer_t *peer = NULL;
|
||||
isc_netaddr_t primaryip;
|
||||
isc_netaddr_fromsockaddr(&primaryip, &xfr->primaryaddr);
|
||||
result = dns_peerlist_peerbyaddr(xfr->view->peers, &primaryip,
|
||||
&peer);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
(void)dns_peer_getsupportedns(peer, &edns);
|
||||
(void)dns_peer_getudpsize(peer, &udpsize);
|
||||
(void)dns_peer_getrequestnsid(peer, &reqnsid);
|
||||
(void)dns_peer_getrequestexpire(peer, &reqexpire);
|
||||
}
|
||||
}
|
||||
|
||||
if (edns) {
|
||||
CHECK(add_opt(msg, udpsize, reqnsid, reqexpire));
|
||||
}
|
||||
|
||||
xfr->nmsg = 0;
|
||||
xfr->nrecs = 0;
|
||||
xfr->nbytes = 0;
|
||||
@@ -1267,6 +1326,38 @@ failure:
|
||||
dns_xfrin_detach(&xfr);
|
||||
}
|
||||
|
||||
static void
|
||||
get_edns_expire(dns_xfrin_t *xfr, dns_message_t *msg) {
|
||||
isc_result_t result;
|
||||
dns_rdata_t rdata = DNS_RDATA_INIT;
|
||||
isc_buffer_t optbuf;
|
||||
uint16_t optcode;
|
||||
uint16_t optlen;
|
||||
|
||||
result = dns_rdataset_first(msg->opt);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
dns_rdataset_current(msg->opt, &rdata);
|
||||
isc_buffer_init(&optbuf, rdata.data, rdata.length);
|
||||
isc_buffer_add(&optbuf, rdata.length);
|
||||
while (isc_buffer_remaininglength(&optbuf) >= 4) {
|
||||
optcode = isc_buffer_getuint16(&optbuf);
|
||||
optlen = isc_buffer_getuint16(&optbuf);
|
||||
/*
|
||||
* A EDNS EXPIRE response has a length of 4.
|
||||
*/
|
||||
if (optcode != DNS_OPT_EXPIRE || optlen != 4) {
|
||||
isc_buffer_forward(&optbuf, optlen);
|
||||
continue;
|
||||
}
|
||||
xfr->expireopt = isc_buffer_getuint32(&optbuf);
|
||||
xfr->expireoptset = true;
|
||||
dns_zone_log(xfr->zone, ISC_LOG_DEBUG(1),
|
||||
"got EDNS EXPIRE of %u", xfr->expireopt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
|
||||
dns_xfrin_t *xfr = (dns_xfrin_t *)arg;
|
||||
@@ -1324,7 +1415,17 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
|
||||
if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
|
||||
msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass)
|
||||
{
|
||||
if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror)
|
||||
if (result == ISC_R_SUCCESS &&
|
||||
msg->rcode == dns_rcode_formerr && xfr->edns &&
|
||||
(xfr->state == XFRST_SOAQUERY ||
|
||||
xfr->state == XFRST_INITIALSOA))
|
||||
{
|
||||
xfr->edns = false;
|
||||
dns_message_detach(&msg);
|
||||
xfrin_reset(xfr);
|
||||
goto try_again;
|
||||
} else if (result == ISC_R_SUCCESS &&
|
||||
msg->rcode != dns_rcode_noerror)
|
||||
{
|
||||
result = dns_result_fromrcode(msg->rcode);
|
||||
} else if (result == ISC_R_SUCCESS &&
|
||||
@@ -1353,6 +1454,7 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
|
||||
xfrin_reset(xfr);
|
||||
xfr->reqtype = dns_rdatatype_soa;
|
||||
xfr->state = XFRST_SOAQUERY;
|
||||
try_again:
|
||||
result = xfrin_start(xfr);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
xfrin_fail(xfr, result, "failed setting up socket");
|
||||
@@ -1518,6 +1620,10 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
|
||||
xfr->tsigctx = msg->tsigctx;
|
||||
msg->tsigctx = NULL;
|
||||
|
||||
if (!xfr->expireoptset && msg->opt != NULL) {
|
||||
get_edns_expire(xfr, msg);
|
||||
}
|
||||
|
||||
switch (xfr->state) {
|
||||
case XFRST_GOTSOA:
|
||||
xfr->reqtype = dns_rdatatype_axfr;
|
||||
@@ -1545,7 +1651,9 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
|
||||
if (xfr->done != NULL) {
|
||||
LIBDNS_XFRIN_DONE_CALLBACK_BEGIN(xfr, xfr->info,
|
||||
result);
|
||||
(xfr->done)(xfr->zone, ISC_R_SUCCESS);
|
||||
(xfr->done)(xfr->zone,
|
||||
xfr->expireoptset ? &xfr->expireopt : NULL,
|
||||
ISC_R_SUCCESS);
|
||||
xfr->done = NULL;
|
||||
LIBDNS_XFRIN_DONE_CALLBACK_END(xfr, xfr->info, result);
|
||||
}
|
||||
|
||||
+80
-14
@@ -862,7 +862,7 @@ zone_catz_disable(dns_zone_t *zone);
|
||||
static isc_result_t
|
||||
default_journal(dns_zone_t *zone);
|
||||
static void
|
||||
zone_xfrdone(dns_zone_t *zone, isc_result_t result);
|
||||
zone_xfrdone(dns_zone_t *zone, uint32_t *expireopt, isc_result_t result);
|
||||
static isc_result_t
|
||||
zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime,
|
||||
isc_result_t result);
|
||||
@@ -11390,6 +11390,27 @@ dump_done(void *arg, isc_result_t result) {
|
||||
|
||||
ENTER;
|
||||
|
||||
/*
|
||||
* Adjust modification time of zone file to preserve expire timing.
|
||||
*/
|
||||
if ((zone->type == dns_zone_secondary ||
|
||||
zone->type == dns_zone_mirror ||
|
||||
zone->type == dns_zone_redirect) &&
|
||||
result == ISC_R_SUCCESS)
|
||||
{
|
||||
LOCK_ZONE(zone);
|
||||
isc_time_t when;
|
||||
isc_interval_t i;
|
||||
isc_interval_set(&i, zone->expire, 0);
|
||||
result = isc_time_subtract(&zone->expiretime, &i, &when);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
(void)isc_file_settime(zone->masterfile, &when);
|
||||
} else {
|
||||
result = ISC_R_SUCCESS;
|
||||
}
|
||||
UNLOCK_ZONE(zone);
|
||||
}
|
||||
|
||||
if (result == ISC_R_SUCCESS && zone->journal != NULL) {
|
||||
/*
|
||||
* We don't own these, zone->dctx must stay valid.
|
||||
@@ -11573,6 +11594,22 @@ redo:
|
||||
} else {
|
||||
result = dns_master_dump(zone->mctx, db, version, masterstyle,
|
||||
masterfile, masterformat, &rawdata);
|
||||
if ((zone->type == dns_zone_secondary ||
|
||||
zone->type == dns_zone_mirror ||
|
||||
zone->type == dns_zone_redirect) &&
|
||||
result == ISC_R_SUCCESS)
|
||||
{
|
||||
isc_time_t when;
|
||||
isc_interval_t i;
|
||||
isc_interval_set(&i, zone->expire, 0);
|
||||
result = isc_time_subtract(&zone->expiretime, &i,
|
||||
&when);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
(void)isc_file_settime(zone->masterfile, &when);
|
||||
} else {
|
||||
result = ISC_R_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
fail:
|
||||
if (version != NULL) {
|
||||
@@ -17079,20 +17116,23 @@ zone_detachdb(dns_zone_t *zone) {
|
||||
}
|
||||
|
||||
static void
|
||||
zone_xfrdone(dns_zone_t *zone, isc_result_t result) {
|
||||
isc_time_t now;
|
||||
zone_xfrdone(dns_zone_t *zone, uint32_t *expireopt, isc_result_t result) {
|
||||
isc_time_t now, expiretime;
|
||||
bool again = false;
|
||||
unsigned int soacount;
|
||||
unsigned int nscount;
|
||||
uint32_t serial, refresh, retry, expire, minimum, soattl;
|
||||
uint32_t serial, refresh, retry, expire, minimum, soattl, oldexpire;
|
||||
isc_result_t xfrresult = result;
|
||||
bool free_needed;
|
||||
dns_zone_t *secure = NULL;
|
||||
|
||||
REQUIRE(DNS_ZONE_VALID(zone));
|
||||
|
||||
dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
|
||||
"zone transfer finished: %s", isc_result_totext(result));
|
||||
dns_zone_logc(
|
||||
zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1),
|
||||
expireopt == NULL ? "zone transfer finished: %s"
|
||||
: "zone transfer finished: %s, expire=%u",
|
||||
isc_result_totext(result), expireopt != NULL ? *expireopt : 0);
|
||||
|
||||
/*
|
||||
* Obtaining a lock on the zone->secure (see zone_send_secureserial)
|
||||
@@ -17133,6 +17173,8 @@ again:
|
||||
goto same_primary;
|
||||
}
|
||||
|
||||
oldexpire = zone->expire;
|
||||
|
||||
/*
|
||||
* Update the zone structure's data from the actual
|
||||
* SOA received.
|
||||
@@ -17185,19 +17227,31 @@ again:
|
||||
}
|
||||
|
||||
/*
|
||||
* Set our next update/expire times.
|
||||
* Set our next refresh time.
|
||||
*/
|
||||
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) {
|
||||
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH);
|
||||
zone->refreshtime = now;
|
||||
DNS_ZONE_TIME_ADD(&now, zone->expire,
|
||||
&zone->expiretime);
|
||||
} else {
|
||||
DNS_ZONE_JITTER_ADD(&now, zone->refresh,
|
||||
&zone->refreshtime);
|
||||
DNS_ZONE_TIME_ADD(&now, zone->expire,
|
||||
&zone->expiretime);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set our next expire time. If the parent returned
|
||||
* an EXPIRE option use that to update zone->expiretime.
|
||||
*/
|
||||
expire = zone->expire;
|
||||
if (expireopt != NULL && *expireopt < expire) {
|
||||
expire = *expireopt;
|
||||
}
|
||||
DNS_ZONE_TIME_ADD(&now, expire, &expiretime);
|
||||
if (oldexpire != zone->expire ||
|
||||
isc_time_compare(&expiretime, &zone->expiretime) > 0)
|
||||
{
|
||||
zone->expiretime = expiretime;
|
||||
}
|
||||
|
||||
if (result == ISC_R_SUCCESS && xfrresult == ISC_R_SUCCESS) {
|
||||
char buf[DNS_NAME_FORMATSIZE + sizeof(": TSIG ''")];
|
||||
if (zone->tsigkey != NULL) {
|
||||
@@ -17224,15 +17278,27 @@ again:
|
||||
*/
|
||||
if (zone->masterfile != NULL || zone->journal != NULL) {
|
||||
unsigned int delay = DNS_DUMP_DELAY;
|
||||
isc_interval_t i;
|
||||
isc_time_t when;
|
||||
|
||||
/*
|
||||
* Compute effective modification time.
|
||||
*/
|
||||
isc_interval_set(&i, zone->expire, 0);
|
||||
result = isc_time_subtract(&zone->expiretime, &i,
|
||||
&when);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
when = now;
|
||||
}
|
||||
|
||||
result = ISC_R_FAILURE;
|
||||
if (zone->journal != NULL) {
|
||||
result = isc_file_settime(zone->journal, &now);
|
||||
result = isc_file_settime(zone->journal, &when);
|
||||
}
|
||||
if (result != ISC_R_SUCCESS && zone->masterfile != NULL)
|
||||
{
|
||||
result = isc_file_settime(zone->masterfile,
|
||||
&now);
|
||||
&when);
|
||||
}
|
||||
|
||||
if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NODELAY) != 0) ||
|
||||
@@ -17715,7 +17781,7 @@ failure:
|
||||
* zmgr->xfrin_in_progress.
|
||||
*/
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
zone_xfrdone(zone, result);
|
||||
zone_xfrdone(zone, NULL, result);
|
||||
}
|
||||
|
||||
if (zmgr_tlsctx_cache != NULL) {
|
||||
|
||||
Reference in New Issue
Block a user