Commit c8aa7ce7 authored by Evan Hunt's avatar Evan Hunt

2732. [func] Add optional filter-aaaa-on-v4 option, available

			if built with './configure --enable-filter-aaaa'.
			Filters out AAAA answers to clients connecting
			via IPv4.  (This is NOT recommended for general
			use.) [RT #20339]
parent c0214996
2732. [func] Add optional filter-aaaa-on-v4 option, available
if built with './configure --enable-filter-aaaa'.
Filters out AAAA answers to clients connecting
via IPv4. (This is NOT recommended for general
use.) [RT #20339]
2731. [func] Additional work on change 2709. The key parser
will now ignore unrecognized fields when the
minor version number of the private key format
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: client.c,v 1.265 2009/05/07 09:41:21 fdupont Exp $ */
/* $Id: client.c,v 1.266 2009/10/26 23:14:53 each Exp $ */
#include <config.h>
......@@ -918,7 +918,7 @@ ns_client_send(ns_client_t *client) {
dns_compress_t cctx;
isc_boolean_t cleanup_cctx = ISC_FALSE;
unsigned char sendbuf[SEND_BUFFER_SIZE];
unsigned int dnssec_opts;
unsigned int render_opts;
unsigned int preferred_glue;
isc_boolean_t opt_included = ISC_FALSE;
......@@ -930,10 +930,21 @@ ns_client_send(ns_client_t *client) {
client->message->flags |= DNS_MESSAGEFLAG_RA;
if ((client->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0)
dnssec_opts = 0;
render_opts = 0;
else
dnssec_opts = DNS_MESSAGERENDER_OMITDNSSEC;
render_opts = DNS_MESSAGERENDER_OMITDNSSEC;
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* filter-aaaa-on-v4 yes or break-dnssec option to suppress
* AAAA records
* We already know that request came via IPv4,
* that we have both AAAA and A records,
* and that we either have no signatures that the client wants
* or we are supposed to break DNSSEC.
*/
if ((client->attributes & NS_CLIENTATTR_FILTER_AAAA) != 0)
render_opts |= DNS_MESSAGERENDER_FILTER_AAAA;
#endif
preferred_glue = 0;
if (client->view != NULL) {
if (client->view->preferred_glue == dns_rdatatype_a)
......@@ -977,7 +988,7 @@ ns_client_send(ns_client_t *client) {
result = dns_message_rendersection(client->message,
DNS_SECTION_ANSWER,
DNS_MESSAGERENDER_PARTIAL |
dnssec_opts);
render_opts);
if (result == ISC_R_NOSPACE) {
client->message->flags |= DNS_MESSAGEFLAG_TC;
goto renderend;
......@@ -987,7 +998,7 @@ ns_client_send(ns_client_t *client) {
result = dns_message_rendersection(client->message,
DNS_SECTION_AUTHORITY,
DNS_MESSAGERENDER_PARTIAL |
dnssec_opts);
render_opts);
if (result == ISC_R_NOSPACE) {
client->message->flags |= DNS_MESSAGEFLAG_TC;
goto renderend;
......@@ -996,7 +1007,7 @@ ns_client_send(ns_client_t *client) {
goto done;
result = dns_message_rendersection(client->message,
DNS_SECTION_ADDITIONAL,
preferred_glue | dnssec_opts);
preferred_glue | render_opts);
if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE)
goto done;
renderend:
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: config.c,v 1.103 2009/10/10 01:47:59 each Exp $ */
/* $Id: config.c,v 1.104 2009/10/26 23:14:53 each Exp $ */
/*! \file */
......@@ -158,6 +158,10 @@ options {\n\
zero-no-soa-ttl-cache no;\n\
nsec3-test-zone no;\n\
"
#ifdef ALLOW_FILTER_AAAA_ON_V4
" filter-aaaa-on-v4 no;\n\
"
#endif
" /* zone */\n\
allow-query {any;};\n\
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: client.h,v 1.90 2009/05/07 09:41:22 fdupont Exp $ */
/* $Id: client.h,v 1.91 2009/10/26 23:14:53 each Exp $ */
#ifndef NAMED_CLIENT_H
#define NAMED_CLIENT_H 1
......@@ -168,6 +168,10 @@ struct ns_client {
#define NS_CLIENTATTR_MULTICAST 0x08 /*%< recv'd from multicast */
#define NS_CLIENTATTR_WANTDNSSEC 0x10 /*%< include dnssec records */
#define NS_CLIENTATTR_WANTNSID 0x20 /*%< include nameserver ID */
#ifdef ALLOW_FILTER_AAAA_ON_V4
#define NS_CLIENTATTR_FILTER_AAAA 0x40 /*%< suppress AAAAs */
#define NS_CLIENTATTR_FILTER_AAAA_RC 0x80 /*%< recursing for A against AAAA */
#endif
extern unsigned int ns_client_requests;
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: server.h,v 1.102 2009/10/12 20:48:11 each Exp $ */
/* $Id: server.h,v 1.103 2009/10/26 23:14:53 each Exp $ */
#ifndef NAMED_SERVER_H
#define NAMED_SERVER_H 1
......@@ -115,6 +115,9 @@ struct ns_server {
dns_name_t *session_keyname;
unsigned int session_keyalg;
isc_uint16_t session_keybits;
#ifdef ALLOW_FILTER_AAAA_ON_V4
dns_v4_aaaa_t v4_aaaa;
#endif
};
#define NS_SERVER_MAGIC ISC_MAGIC('S','V','E','R')
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: query.c,v 1.328 2009/10/24 04:38:19 marka Exp $ */
/* $Id: query.c,v 1.329 2009/10/26 23:14:53 each Exp $ */
/*! \file */
......@@ -4637,6 +4637,20 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
}
if (type == dns_rdatatype_any) {
#ifdef ALLOW_FILTER_AAAA_ON_V4
isc_boolean_t have_aaaa, have_a, have_sig;
/*
* The filter-aaaa-on-v4 option should
* suppress AAAAs for IPv4 clients if there is an A.
* If we are not authoritative, assume there is a A
* even in if it is not in our cache. This assumption could
* be wrong but it is a good bet.
*/
have_aaaa = ISC_FALSE;
have_a = !authoritative;
have_sig = ISC_FALSE;
#endif
/*
* XXXRTH Need to handle zonecuts with special case
* code.
......@@ -4664,6 +4678,20 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
result = dns_rdatasetiter_first(rdsiter);
while (result == ISC_R_SUCCESS) {
dns_rdatasetiter_current(rdsiter, rdataset);
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* Notice the presence of A and AAAAs so
* that AAAAs can be hidden from IPv4 clients.
*/
if (ns_g_server->v4_aaaa != dns_v4_aaaa_ok &&
client->peeraddr_valid &&
client->peeraddr.type.sa.sa_family == AF_INET) {
if (rdataset->type == dns_rdatatype_aaaa)
have_aaaa = ISC_TRUE;
else if (rdataset->type == dns_rdatatype_a)
have_a = ISC_TRUE;
}
#endif
if (is_zone && qtype == dns_rdatatype_any &&
!dns_db_issecure(db) &&
dns_rdatatype_isdnssec(rdataset->type)) {
......@@ -4675,6 +4703,10 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
dns_rdataset_disassociate(rdataset);
} else if ((qtype == dns_rdatatype_any ||
rdataset->type == qtype) && rdataset->type != 0) {
#ifdef ALLOW_FILTER_AAAA_ON_V4
if (dns_rdatatype_isdnssec(rdataset->type))
have_sig = ISC_TRUE;
#endif
if (NOQNAME(rdataset) && WANTDNSSEC(client))
noqname = rdataset;
else
......@@ -4705,6 +4737,16 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
result = dns_rdatasetiter_next(rdsiter);
}
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* Filter AAAAs if there is an A and there is no signature
* or we are supposed to break DNSSEC.
*/
if (have_aaaa && have_a &&
(!have_sig || !WANTDNSSEC(client) ||
ns_g_server->v4_aaaa == dns_v4_aaaa_break_dnssec))
client->attributes |= NS_CLIENTATTR_FILTER_AAAA;
#endif
if (fname != NULL)
dns_message_puttempname(client->message, &fname);
......@@ -4766,6 +4808,90 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
* This is the "normal" case -- an ordinary question to which
* we know the answer.
*/
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* Optionally hide AAAAs from IPv4 clients if there is an A.
* We add the AAAAs now, but might refuse to render them later
* after DNSSEC is figured out.
* This could be more efficient, but the whole idea is
* so fundamentally wrong, unavoidably inaccurate, and
* unneeded that it is best to keep it as short as possible.
*/
if (ns_g_server->v4_aaaa != dns_v4_aaaa_ok &&
client->peeraddr_valid &&
client->peeraddr.type.sa.sa_family == AF_INET &&
(!WANTDNSSEC(client) ||
sigrdataset == NULL ||
!dns_rdataset_isassociated(sigrdataset) ||
ns_g_server->v4_aaaa == dns_v4_aaaa_break_dnssec)) {
if (qtype == dns_rdatatype_aaaa) {
trdataset = query_newrdataset(client);
result = dns_db_findrdataset(db, node, version,
dns_rdatatype_a, 0,
client->now,
trdataset, NULL);
if (dns_rdataset_isassociated(trdataset))
dns_rdataset_disassociate(trdataset);
query_putrdataset(client, &trdataset);
/*
* We have an AAAA but the A is not in our cache.
* Assume any result other than DNS_R_DELEGATION
* or ISC_R_NOTFOUND means there is no A and
* so AAAAs are ok.
* Assume there is no A if we can't recurse
* for this client, although that could be
* the wrong answer. What else can we do?
* Besides, that we have the AAAA and are using
* this mechanism suggests that we care more
* about As than AAAAs and would have cached
* the A if it existed.
*/
if (result == ISC_R_SUCCESS) {
client->attributes |=
NS_CLIENTATTR_FILTER_AAAA;
} else if (authoritative ||
!RECURSIONOK(client) ||
(result != DNS_R_DELEGATION &&
result != ISC_R_NOTFOUND)) {
client->attributes &=
~NS_CLIENTATTR_FILTER_AAAA;
} else {
/*
* This is an ugly kludge to recurse
* for the A and discard the result.
*
* Continue to add the AAAA now.
* We'll make a note to not render it
* if the recursion for the A succeeds.
*/
result = query_recurse(client,
dns_rdatatype_a,
NULL, NULL, resuming);
if (result == ISC_R_SUCCESS) {
client->attributes |=
NS_CLIENTATTR_FILTER_AAAA_RC;
client->query.attributes |=
NS_QUERYATTR_RECURSING;
}
}
} else if (qtype == dns_rdatatype_a &&
(client->attributes &
NS_CLIENTATTR_FILTER_AAAA_RC) != 0) {
client->attributes &=
~NS_CLIENTATTR_FILTER_AAAA_RC;
client->attributes |=
NS_CLIENTATTR_FILTER_AAAA;
dns_rdataset_disassociate(rdataset);
if (sigrdataset != NULL &&
dns_rdataset_isassociated(sigrdataset))
dns_rdataset_disassociate(sigrdataset);
goto cleanup;
}
}
#endif
if (sigrdataset != NULL)
sigrdatasetp = &sigrdataset;
else
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: server.c,v 1.552 2009/10/20 03:15:06 marka Exp $ */
/* $Id: server.c,v 1.553 2009/10/26 23:14:53 each Exp $ */
/*! \file */
......@@ -4355,6 +4355,25 @@ load_configuration(const char *filename, ns_server_t *server,
server->flushonshutdown = ISC_FALSE;
}
#ifdef ALLOW_FILTER_AAAA_ON_V4
obj = NULL;
result = ns_config_get(maps, "filter-aaaa-on-v4", &obj);
INSIST(result == ISC_R_SUCCESS);
if (cfg_obj_isboolean(obj)) {
if (cfg_obj_asboolean(obj))
server->v4_aaaa = dns_v4_aaaa_filter;
else
server->v4_aaaa = dns_v4_aaaa_ok;
} else {
const char *v4_aaaastr = cfg_obj_asstring(obj);
if (strcasecmp(v4_aaaastr, "break-dnssec") == 0)
server->v4_aaaa
= dns_v4_aaaa_break_dnssec;
else
INSIST(0);
}
#endif
result = ISC_R_SUCCESS;
cleanup:
......
......@@ -16,7 +16,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: config.h.in,v 1.120 2009/09/01 18:40:25 jinmei Exp $ */
/* $Id: config.h.in,v 1.121 2009/10/26 23:14:53 each Exp $ */
/*! \file */
......@@ -365,3 +365,7 @@ int sigwait(const unsigned int *set, int *sig);
/* Define to empty if the keyword `volatile' does not work. Warning: valid
code using `volatile' can become incorrect without. Disable with care. */
#undef volatile
/* Define to enable the "filter-aaaa-on-v4" option. */
#undef ALLOW_FILTER_AAAA_ON_V4
......@@ -18,7 +18,7 @@ AC_DIVERT_PUSH(1)dnl
esyscmd([sed "s/^/# /" COPYRIGHT])dnl
AC_DIVERT_POP()dnl
AC_REVISION($Revision: 1.485 $)
AC_REVISION($Revision: 1.486 $)
AC_INIT(lib/dns/name.c)
AC_PREREQ(2.59)
......@@ -2641,6 +2641,25 @@ case "$enable_fixed" in
;;
esac
#
# Activate "filter-aaaa-on-v4" or not?
#
AC_ARG_ENABLE(filter-aaaa,
[ --enable-filter-aaaa enable filtering of AAAA records over IPv4
[[default=no]]],
enable_filter="$enableval",
enable_filter="no")
case "$enable_filter" in
yes)
AC_DEFINE(ALLOW_FILTER_AAAA_ON_V4, 1,
[Define to enable the "filter-aaaa-on-v4" option.])
;;
no)
;;
*)
;;
esac
#
# The following sets up how non-blocking i/o is established.
# Sunos, cygwin and solaris 2.x (x<5) require special handling.
......
......@@ -18,7 +18,7 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
<!-- File: $Id: Bv9ARM-book.xml,v 1.439 2009/10/22 03:43:16 each Exp $ -->
<!-- File: $Id: Bv9ARM-book.xml,v 1.440 2009/10/26 23:14:53 each Exp $ -->
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<title>BIND 9 Administrator Reference Manual</title>
......@@ -5000,6 +5000,8 @@ badresp:1,adberr:0,findfail:0,valfail:0]
<optional> random-device <replaceable>path_name</replaceable> ; </optional>
<optional> max-cache-size <replaceable>size_spec</replaceable> ; </optional>
<optional> match-mapped-addresses <replaceable>yes_or_no</replaceable>; </optional>
<optional> match-mapped-addresses <replaceable>yes_or_no</replaceable>; </optional>
<optional> disable-aaaa-on-v4-transport ( <replaceable>yes_or_no</replaceable> | <replaceable>break-dnssec</replaceable> ); </optional>
<optional> preferred-glue ( <replaceable>A</replaceable> | <replaceable>AAAA</replaceable> | <replaceable>NONE</replaceable> ); </optional>
<optional> edns-udp-size <replaceable>number</replaceable>; </optional>
<optional> max-udp-size <replaceable>number</replaceable>; </optional>
......@@ -6232,6 +6234,59 @@ options {
</listitem>
</varlistentry>
<varlistentry>
<term><command>filter-aaaa-on-v4</command></term>
<listitem>
<para>
This option is only available when
<acronym>BIND</acronym> 9 is compiled with the
<userinput>--with-filter-aaaa</userinput> option on the
"configure" command line. It is intended to help the
transition from IPv4 to IPv6 by not giving IPv6 addresses
to DNS clients unless they have connections to the IPv6
Internet. This is not recommended unless absolutely
necessary. The default is <userinput>no</userinput>.
</para>
<para>
If <userinput>yes</userinput>,
the DNS client is at an IPv4 address,
and if the response does not include DNSSEC signatures,
then all AAAA records are deleted from the response.
This filtering applies to all responses and not only
authoritative responses.
</para>
<para>
If <userinput>break-dnssec</userinput>,
then AAAA records are deleted even when dnssec is enabled.
As suggested by the name, this makes the response not verify,
because the DNSSEC protocol is designed detect deletions.
</para>
<para>
This mechanism can erroneously cause other servers to
not give AAAA records to their clients.
A recursing server with both IPv6 and IPv4 network connections
that queries an authoritative server using this mechanism
via IPv4 will be denied AAAA records even if its client is
using IPv6.
</para>
<para>
This mechanism is applied to authoritative as well as
non-authoritative records.
A client using IPv4 that is not allowed recursion can
erroneously be given AAAA records because the server is not
allowed to check for A records.
</para>
<para>
Some AAAA records are given to IPv4 clients in glue records.
IPv4 clients that are servers can then erroneously
answer requests for AAAA records received via IPv4.
</para>
<para>
security
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>ixfr-from-differences</command></term>
<listitem>
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: message.h,v 1.128 2009/09/01 00:22:26 jinmei Exp $ */
/* $Id: message.h,v 1.129 2009/10/26 23:14:54 each Exp $ */
#ifndef DNS_MESSAGE_H
#define DNS_MESSAGE_H 1
......@@ -173,6 +173,9 @@ typedef int dns_messagetextflag_t;
additional section. */
#define DNS_MESSAGERENDER_PREFER_AAAA 0x0010 /*%< prefer AAAA records in
additional section. */
#ifdef ALLOW_FILTER_AAAA_ON_V4
#define DNS_MESSAGERENDER_FILTER_AAAA 0x0020 /*%< filter AAAA records */
#endif
typedef struct dns_msgblock dns_msgblock_t;
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: types.h,v 1.136 2009/09/01 00:22:27 jinmei Exp $ */
/* $Id: types.h,v 1.137 2009/10/26 23:14:54 each Exp $ */
#ifndef DNS_TYPES_H
#define DNS_TYPES_H 1
......@@ -187,6 +187,15 @@ typedef enum {
dns_masterformat_raw = 2
} dns_masterformat_t;
#ifdef ALLOW_FILTER_AAAA_ON_V4
typedef enum {
dns_v4_aaaa_ok = 0,
dns_v4_aaaa_filter = 1,
dns_v4_aaaa_break_dnssec = 2
} dns_v4_aaaa_t;
#endif
/*
* These are generated by gen.c.
*/
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: message.c,v 1.247 2009/01/17 23:47:42 tbox Exp $ */
/* $Id: message.c,v 1.248 2009/10/26 23:14:54 each Exp $ */
/*! \file */
......@@ -1802,6 +1802,36 @@ wrong_priority(dns_rdataset_t *rds, int pass, dns_rdatatype_t preferred_glue) {
return (ISC_TRUE);
}
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* Decide whether to not answer with an AAAA record and its RRSIG
*/
static inline isc_boolean_t
norender_rdataset(const dns_rdataset_t *rdataset, unsigned int options)
{
switch (rdataset->type) {
case dns_rdatatype_aaaa:
if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0)
return (ISC_FALSE);
break;
case dns_rdatatype_rrsig:
if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 ||
rdataset->covers != dns_rdatatype_aaaa)
return (ISC_FALSE);
break;
default:
return (ISC_FALSE);
}
if (rdataset->rdclass != dns_rdataclass_in)
return (ISC_FALSE);
return (ISC_TRUE);
}
#endif
isc_result_t
dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
unsigned int options)
......@@ -1927,6 +1957,23 @@ dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
preferred_glue))
goto next;
#ifdef ALLOW_FILTER_AAAA_ON_V4
/*
* Suppress AAAAs if asked and we are
* not doing DNSSEC or are breaking DNSSEC.
* Say so in the AD bit if we break DNSSEC.
*/
if (norender_rdataset(rdataset, options) &&
sectionid != DNS_SECTION_QUESTION) {
if (sectionid == DNS_SECTION_ANSWER ||
sectionid == DNS_SECTION_AUTHORITY)
msg->flags &= ~DNS_MESSAGEFLAG_AD;
if (OPTOUT(rdataset))
msg->flags &= ~DNS_MESSAGEFLAG_AD;
goto next;
}
#endif
st = *(msg->buffer);
count = 0;
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: namedconf.c,v 1.109 2009/10/12 23:48:02 tbox Exp $ */
/* $Id: namedconf.c,v 1.110 2009/10/26 23:14:54 each Exp $ */
/*! \file */
......@@ -120,6 +120,9 @@ static cfg_type_t cfg_type_zone;
static cfg_type_t cfg_type_zoneopts;
static cfg_type_t cfg_type_dynamically_loadable_zones;
static cfg_type_t cfg_type_dynamically_loadable_zones_opts;
#ifdef ALLOW_FILTER_AAAA_ON_V4
static cfg_type_t cfg_type_v4_aaaa;
#endif
/*
* Clauses that can be found in a 'dynamically loadable zones' statement
......@@ -874,6 +877,9 @@ options_clauses[] = {
{ "use-ixfr", &cfg_type_boolean, 0 },
{ "version", &cfg_type_qstringornone, 0 },
{ "flush-zones-on-shutdown", &cfg_type_boolean, 0 },
#ifdef ALLOW_FILTER_AAAA_ON_V4
{ "filter-aaaa-on-v4", &cfg_type_v4_aaaa, 0 },
#endif
{ NULL, NULL, 0 }
};
......@@ -1591,6 +1597,19 @@ static cfg_type_t cfg_type_ixfrdifftype = {
&cfg_rep_string, ixfrdiff_enums,
};
#ifdef ALLOW_FILTER_AAAA_ON_V4
static const char *v4_aaaa_enums[] = { "break-dnssec", NULL };
static isc_result_t
parse_v4_aaaa(cfg_parser_t *pctx, const cfg_type_t *type,
cfg_obj_t **ret) {
return (parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
}
static cfg_type_t cfg_type_v4_aaaa = {
"v4_aaaa", parse_v4_aaaa, cfg_print_ustring,
doc_enum_or_other, &cfg_rep_string, v4_aaaa_enums,
};
#endif
static keyword_type_t key_kw = { "key", &cfg_type_astring };
LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_keyref = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment