Commit 0fc2f313 authored by Simon Kelley's avatar Simon Kelley

First functional DNSSEC - highly alpha.

parent c3e0b9b6
...@@ -56,6 +56,8 @@ static const struct { ...@@ -56,6 +56,8 @@ static const struct {
{ 38, "A6" }, { 38, "A6" },
{ 39, "DNAME" }, { 39, "DNAME" },
{ 41, "OPT" }, { 41, "OPT" },
{ 43, "DS" },
{ 46, "RRSIG" },
{ 48, "DNSKEY" }, { 48, "DNSKEY" },
{ 249, "TKEY" }, { 249, "TKEY" },
{ 250, "TSIG" }, { 250, "TSIG" },
...@@ -916,12 +918,19 @@ void cache_reload(void) ...@@ -916,12 +918,19 @@ void cache_reload(void)
struct name_list *nl; struct name_list *nl;
struct cname *a; struct cname *a;
struct interface_name *intr; struct interface_name *intr;
#ifdef HAVE_DNSSEC
struct dnskey *key;
#endif
cache_inserted = cache_live_freed = 0; cache_inserted = cache_live_freed = 0;
for (i=0; i<hash_size; i++) for (i=0; i<hash_size; i++)
for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
{ {
#ifdef HAVE_DNSSEC
if (cache->flags & (F_DNSKEY | F_DS))
blockdata_free(cache->addr.key.keydata);
#endif
tmp = cache->hash_next; tmp = cache->hash_next;
if (cache->flags & (F_HOSTS | F_CONFIG)) if (cache->flags & (F_HOSTS | F_CONFIG))
{ {
...@@ -948,13 +957,27 @@ void cache_reload(void) ...@@ -948,13 +957,27 @@ void cache_reload(void)
if (hostname_isequal(a->target, intr->name) && if (hostname_isequal(a->target, intr->name) &&
((cache = whine_malloc(sizeof(struct crec))))) ((cache = whine_malloc(sizeof(struct crec)))))
{ {
cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK;
cache->name.namep = a->alias; cache->name.namep = a->alias;
cache->addr.cname.target.int_name = intr; cache->addr.cname.target.int_name = intr;
cache->addr.cname.uid = -1; cache->addr.cname.uid = -1;
cache_hash(cache); cache_hash(cache);
add_hosts_cname(cache); /* handle chains */ add_hosts_cname(cache); /* handle chains */
} }
#ifdef HAVE_DNSSEC
for (key = daemon->dnskeys; key; key = key->next)
if ((cache = whine_malloc(sizeof(struct crec))) &&
(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
{
cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
cache->name.namep = key->name;
cache->uid = key->keylen;
cache->addr.key.algo = key->algo;
cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
cache_hash(cache);
}
#endif
/* borrow the packet buffer for a temporary by-address hash */ /* borrow the packet buffer for a temporary by-address hash */
memset(daemon->packet, 0, daemon->packet_buff_sz); memset(daemon->packet, 0, daemon->packet_buff_sz);
...@@ -1197,16 +1220,13 @@ void dump_cache(time_t now) ...@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
for (i=0; i<hash_size; i++) for (i=0; i<hash_size; i++)
for (cache = hash_table[i]; cache; cache = cache->hash_next) for (cache = hash_table[i]; cache; cache = cache->hash_next)
{ {
char *a, *p = daemon->namebuff; char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
p += sprintf(p, "%-40.40s ", cache_get_name(cache)); *a = 0;
if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD)) if (strlen(n) == 0)
a = ""; n = "<Root>";
else if (cache->flags & F_CNAME) p += sprintf(p, "%-40.40s ", n);
{ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
a = ""; a = cache_get_cname_target(cache);
if (!is_outdated_cname_pointer(cache))
a = cache_get_cname_target(cache);
}
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
else if (cache->flags & F_DNSKEY) else if (cache->flags & F_DNSKEY)
{ {
...@@ -1216,11 +1236,11 @@ void dump_cache(time_t now) ...@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
else if (cache->flags & F_DS) else if (cache->flags & F_DS)
{ {
a = daemon->addrbuff; a = daemon->addrbuff;
sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag, sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
cache->addr.key.algo, cache->addr.key.digest, cache->uid); cache->addr.key.algo, cache->addr.key.digest);
} }
#endif #endif
else else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
{ {
a = daemon->addrbuff; a = daemon->addrbuff;
if (cache->flags & F_IPV4) if (cache->flags & F_IPV4)
...@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
if (addr) if (addr)
{ {
if (flags & F_KEYTAG)
sprintf(daemon->addrbuff, arg, addr->addr.keytag);
else
{
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
addr, daemon->addrbuff, ADDRSTRLEN); addr, daemon->addrbuff, ADDRSTRLEN);
#else #else
strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
#endif #endif
}
} }
else
dest = arg;
if (flags & F_REVERSE) if (flags & F_REVERSE)
{ {
...@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg; source = arg;
else if (flags & F_UPSTREAM) else if (flags & F_UPSTREAM)
source = "reply"; source = "reply";
else if (flags & F_SECSTAT)
source = "validation";
else if (flags & F_AUTH) else if (flags & F_AUTH)
source = "auth"; source = "auth";
else if (flags & F_SERVER) else if (flags & F_SERVER)
...@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg; source = arg;
verb = "from"; verb = "from";
} }
else if (flags & F_DNSSEC)
{
source = arg;
verb = "to";
}
else else
source = "cached"; source = "cached";
...@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks) ...@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks)
keyblock_free = blocks; keyblock_free = blocks;
} }
} }
void blockdata_retrieve(struct blockdata *block, size_t len, void *data)
{
size_t blen;
struct blockdata *b;
for (b = block; len > 0 && b; b = b->next)
{
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
memcpy(data, b->key, blen);
data += blen;
len -= blen;
}
}
#endif #endif
...@@ -139,8 +139,8 @@ RESOLVFILE ...@@ -139,8 +139,8 @@ RESOLVFILE
/* #define HAVE_DBUS */ /* #define HAVE_DBUS */
/* #define HAVE_IDN */ /* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */ /* #define HAVE_CONNTRACK */
#define HAVE_DNSSEC #define HAVE_DNSSEC
#define HAVE_OPENSSL #define HAVE_OPENSSL
/* Default locations for important system files. */ /* Default locations for important system files. */
...@@ -385,7 +385,12 @@ static char *compile_opts = ...@@ -385,7 +385,12 @@ static char *compile_opts =
#ifndef HAVE_AUTH #ifndef HAVE_AUTH
"no-" "no-"
#endif #endif
"auth"; "auth "
#ifndef HAVE_DNSSEC
"no-"
#endif
"DNSSEC";
#endif #endif
......
...@@ -242,6 +242,7 @@ struct all_addr { ...@@ -242,6 +242,7 @@ struct all_addr {
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
struct in6_addr addr6; struct in6_addr addr6;
#endif #endif
unsigned int keytag;
} addr; } addr;
}; };
...@@ -286,6 +287,12 @@ struct cname { ...@@ -286,6 +287,12 @@ struct cname {
struct cname *next; struct cname *next;
}; };
struct dnskey {
char *name, *key;
int keylen, algo, flags;
struct dnskey *next;
};
#define ADDRLIST_LITERAL 1 #define ADDRLIST_LITERAL 1
#define ADDRLIST_IPV6 2 #define ADDRLIST_IPV6 2
...@@ -360,7 +367,7 @@ struct crec { ...@@ -360,7 +367,7 @@ struct crec {
} key; } key;
} addr; } addr;
time_t ttd; /* time to die */ time_t ttd; /* time to die */
/* used as keylen if F_DS or F_DNSKEY, index to source for F_HOSTS */ /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
int uid; int uid;
unsigned short flags; unsigned short flags;
union { union {
...@@ -395,6 +402,9 @@ struct crec { ...@@ -395,6 +402,9 @@ struct crec {
#define F_QUERY (1u<<19) #define F_QUERY (1u<<19)
#define F_NOERR (1u<<20) #define F_NOERR (1u<<20)
#define F_AUTH (1u<<21) #define F_AUTH (1u<<21)
#define F_DNSSEC (1u<<22)
#define F_KEYTAG (1u<<23)
#define F_SECSTAT (1u<<24)
/* composites */ /* composites */
#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */ #define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
...@@ -896,6 +906,9 @@ extern struct daemon { ...@@ -896,6 +906,9 @@ extern struct daemon {
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
struct prefix_class *prefix_classes; struct prefix_class *prefix_classes;
#endif #endif
#ifdef HAVE_DNSSEC
struct dnskey *dnskeys;
#endif
/* globally used stuff for DNS */ /* globally used stuff for DNS */
char *packet; /* packet buffer */ char *packet; /* packet buffer */
...@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init); ...@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct blockdata *blockdata_alloc(char *data, size_t len); struct blockdata *blockdata_alloc(char *data, size_t len);
size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt);
void blockdata_retrieve(struct blockdata *block, size_t len, void *data);
void blockdata_free(struct blockdata *blocks); void blockdata_free(struct blockdata *blocks);
#endif #endif
...@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, ...@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
unsigned long local_ttl); unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
time_t now, char **ipsets, int is_sign, int checkrebind, time_t now, char **ipsets, int is_sign, int checkrebind,
int checking_disabled); int no_cache, int secure);
size_t answer_request(struct dns_header *header, char *limit, size_t qlen, size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask, time_t now); struct in_addr local_addr, struct in_addr local_netmask, time_t now);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
...@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); ...@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif #endif
/* dnssec.c */ /* dnssec.c */
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type); size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname); int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class); int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo, int keytag);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
/* util.c */ /* util.c */
void rand_init(void); void rand_init(void);
...@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t); ...@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t);
int prettyprint_addr(union mysockaddr *addr, char *buf); int prettyprint_addr(union mysockaddr *addr, char *buf);
int parse_hex(char *in, unsigned char *out, int maxlen, int parse_hex(char *in, unsigned char *out, int maxlen,
unsigned int *wildcard_mask, int *mac_type); unsigned int *wildcard_mask, int *mac_type);
#ifdef HAVE_DNSSEC
int parse_base64(char *in, char *out);
#endif
int memcmp_masked(unsigned char *a, unsigned char *b, int len, int memcmp_masked(unsigned char *a, unsigned char *b, int len,
unsigned int mask); unsigned int mask);
int expand_buf(struct iovec *iov, size_t size); int expand_buf(struct iovec *iov, size_t size);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#ifndef DNSSEC_CRYPTO_H #ifndef DNSSEC_CRYPTO_H
#define DNSSEC_CRYPTO_H #define DNSSEC_CRYPTO_H
struct keydata; struct blockdata;
/* /*
* vtable for a signature verification algorithm. * vtable for a signature verification algorithm.
...@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx; ...@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx;
typedef struct typedef struct
{ {
int digest_algo; int digest_algo;
int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len); int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len);
} VerifyAlg; } VerifyAlg;
struct VerifyAlgCtx struct VerifyAlgCtx
...@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a); ...@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a);
#define DIGESTALG_SHA512 257 #define DIGESTALG_SHA512 257
int digestalg_supported(int algo); int digestalg_supported(int algo);
int digestalg_begin(int algo); void digestalg_begin(int algo);
void digestalg_add_data(void *data, unsigned len); void digestalg_add_data(void *data, unsigned len);
void digestalg_add_keydata(struct keydata *key, size_t len); void digestalg_add_keydata(struct blockdata *key, size_t len);
unsigned char *digestalg_final(void); unsigned char *digestalg_final(void);
int digestalg_len(void); int digestalg_len(void);
......
...@@ -14,13 +14,16 @@ ...@@ -14,13 +14,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <string.h>
#include "dnsmasq.h" #include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h" #include "dnssec-crypto.h"
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/dsa.h> #include <openssl/dsa.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <string.h>
#define POOL_SIZE 1 #define POOL_SIZE 1
static union _Pool static union _Pool
...@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len) ...@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len)
printf("\n"); printf("\n");
} }
static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len) static int keydata_to_bn(BIGNUM *ret, struct blockdata **key_data, unsigned char **p, unsigned len)
{ {
size_t cnt; size_t cnt;
BIGNUM temp; BIGNUM temp;
BN_init(ret); BN_init(ret);
cnt = keydata_walk(key_data, p, len); cnt = blockdata_walk(key_data, p, len);
BN_bin2bn(*p, cnt, ret); BN_bin2bn(*p, cnt, ret);
len -= cnt; len -= cnt;
*p += cnt; *p += cnt;
while (len > 0) while (len > 0)
{ {
if (!(cnt = keydata_walk(key_data, p, len))) if (!(cnt = blockdata_walk(key_data, p, len)))
return 0; return 0;
BN_lshift(ret, ret, cnt*8); BN_lshift(ret, ret, cnt*8);
BN_init(&temp); BN_init(&temp);
...@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char * ...@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char *
return 1; return 1;
} }
static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, unsigned key_len) static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct blockdata *key_data, unsigned key_len)
{ {
unsigned char *p = key_data->key; unsigned char *p = key_data->key;
size_t exp_len, mod_len; size_t exp_len, mod_len;
...@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, ...@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data,
keydata_to_bn(mod, &key_data, &p, mod_len); keydata_to_bn(mod, &key_data, &p, mod_len);
} }
static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata *key_data, unsigned key_len) static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct blockdata *key_data, unsigned key_len)
{ {
unsigned char *p = key_data->key; unsigned char *p = key_data->key;
int T; int T;
...@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct ...@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct
keydata_to_bn(Y, &key_data, &p, 64+T*8); keydata_to_bn(Y, &key_data, &p, 64+T*8);
} }
static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len, int nid, int dlen) static int rsa_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len, int nid, int dlen)
{ {
int validated = 0; int validated = 0;
...@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_ ...@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_
return validated; return validated;
} }
static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) static int rsamd5_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{ {
return rsa_verify(ctx, key_data, key_len, NID_md5, 16); return rsa_verify(ctx, key_data, key_len, NID_md5, 16);
} }
static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{ {
return rsa_verify(ctx, key_data, key_len, NID_sha1, 20); return rsa_verify(ctx, key_data, key_len, NID_sha1, 20);
} }
static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{ {
return rsa_verify(ctx, key_data, key_len, NID_sha256, 32); return rsa_verify(ctx, key_data, key_len, NID_sha256, 32);
} }
static int rsasha512_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{ {
return rsa_verify(ctx, key_data, key_len, NID_sha512, 64); return rsa_verify(ctx, key_data, key_len, NID_sha512, 64);
} }
static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{ {
static unsigned char asn1_signature[] = static unsigned char asn1_signature[] =
{ {
...@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo) ...@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo)
int i; int i;
VerifyAlgCtx *ret = 0; VerifyAlgCtx *ret = 0;
if (!verifyalg_supported(algo))
return 0;
if (pool_used == (1<<POOL_SIZE)-1) if (pool_used == (1<<POOL_SIZE)-1)
ret = whine_malloc(valgctx_size[algo]); ret = whine_malloc(valgctx_size[algo]);
else else
...@@ -271,7 +271,7 @@ int digestalg_supported(int algo) ...@@ -271,7 +271,7 @@ int digestalg_supported(int algo)
algo == DIGESTALG_SHA512); algo == DIGESTALG_SHA512);
} }
int digestalg_begin(int algo) void digestalg_begin(int algo)
{ {
EVP_MD_CTX_init(&digctx); EVP_MD_CTX_init(&digctx);
if (algo == DIGESTALG_SHA1) if (algo == DIGESTALG_SHA1)
...@@ -282,9 +282,6 @@ int digestalg_begin(int algo) ...@@ -282,9 +282,6 @@ int digestalg_begin(int algo)
EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL); EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL);
else if (algo == DIGESTALG_MD5) else if (algo == DIGESTALG_MD5)
EVP_DigestInit_ex(&digctx, EVP_md5(), NULL); EVP_DigestInit_ex(&digctx, EVP_md5(), NULL);
else
return 0;
return 1;
} }
int digestalg_len() int digestalg_len()
...@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len) ...@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len)
EVP_DigestUpdate(&digctx, data, len); EVP_DigestUpdate(&digctx, data, len);
} }
void digestalg_add_keydata(struct keydata *key, size_t len) void digestalg_add_keydata(struct blockdata *key, size_t len)
{ {
size_t cnt; unsigned char *p = NULL; size_t cnt; unsigned char *p = NULL;
while (len) while (len)
{ {
cnt = keydata_walk(&key, &p, len); cnt = blockdata_walk(&key, &p, len);
EVP_DigestUpdate(&digctx, p, cnt); EVP_DigestUpdate(&digctx, p, cnt);
p += cnt; p += cnt;
len -= cnt; len -= cnt;
...@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void) ...@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void)
return digest; return digest;
} }
#endif /* HAVE_DNSSEC */
This diff is collapsed.
...@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, ...@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID)) if (option_bool(OPT_DNSSEC_VALID))
plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ); {
plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
header->hb4 |= HB4_CD;
}
#endif #endif
while (1) while (1)
...@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server ...@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NOERROR); SET_RCODE(header, NOERROR);
} }
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache)) if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure))
{ {
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1; munged = 1;
...@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now) ...@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now)
if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{ {
int status; int status;
int class;
/* We've had a reply already, which we're validating. Ignore this duplicate */
if (forward->stash)
return;
if (forward->flags & FREC_DNSKEY_QUERY) if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else else
status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
/* Can't validate, as we're missing key data. Put this /* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */ answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY) if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{ {
struct frec *new; struct frec *new;
if ((forward->stash = blockdata_alloc((char *)header, n)))
if ((new = get_new_frec(now, NULL, 1)))
{ {
forward->stash_len = n; struct frec *next = new->next;
*new = *forward; /* copy everything, then overwrite */
new->next = next;
new->stash = NULL;
new->blocking_query = NULL;
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
if ((new = get_new_frec(now, NULL, 1))) if ((forward->stash = blockdata_alloc((char *)header, n)))
{ {
int fd; int fd;
new = forward; /* copy everything, then overwrite */ forward->stash_len = n;
new->dependent = forward; /* to find query awaiting new one. */ new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */ forward->blocking_query = new; /* for garbage cleaning */
/* validate routines leave name of required record in daemon->namebuff */ /* validate routines leave name of required record in daemon->keyname */
if (status == STAT_NEED_KEY) if (status == STAT_NEED_KEY)
{ {
new->flags |= FREC_DNSKEY_QUERY; new->flags |= FREC_DNSKEY_QUERY;
nn = dnssec_generate_query(header, daemon->namebuff, class, T_DNSKEY); nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DNSKEY, &server->addr);
} }
else if (status == STAT_NEED_DS) else if (status == STAT_NEED_DS)
{ {
new->flags |= FREC_DS_QUERY; new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header, daemon->namebuff, class, T_DS); nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DS, &server->addr);
} }
new->crc = questions_crc(header, nn, daemon->namebuff); new->crc = questions_crc(header, nn, daemon->namebuff);
new->new_id = get_id(new->crc); new->new_id = get_id(new->crc);
...@@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now) ...@@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now)
} }
/* Send DNSSEC query to same server as original query */ /* Send DNSSEC query to same server as original query */
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send()); while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
server->queries++;
} }
} }
return; return;
} }
...@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now) ...@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now)
and validate them with the new data. Failure to find needed data here is an internal error. and validate them with the new data. Failure to find needed data here is an internal error.
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */ return it to the original requestor. */
while (forward->dependent) if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
{ {
struct frec *prev = forward->dependent; while (forward->dependent)
free_frec(forward);
forward = prev;
blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
if (status == STAT_SECURE)
{ {
if (forward->flags & FREC_DNSKEY_QUERY) struct frec *prev;
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY) if (status == STAT_SECURE)
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); {
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
}
prev = forward->dependent;
free_frec(forward);
forward = prev;
forward->blocking_query = NULL; /* already gone */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
}
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if (status == STAT_SECURE)
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
status = STAT_INSECURE;
} }
} }
/* All DNSKEY and DS records done and in cache, now finally validate original log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
answer, provided last DNSKEY is OK. */ status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
if (status == STAT_SECURE)
status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); no_cache_dnssec = 0;
if (status == STAT_SECURE) if (status == STAT_SECURE)
cache_secure = 1; cache_secure = 1;
/* TODO return SERVFAIL here */ /* TODO return SERVFAIL here */
else if (status == STAT_BOGUS) else if (status == STAT_BOGUS)
no_cache_dnssec = 1; no_cache_dnssec = 1;
/* restore CD bit to the value in the query */
if (forward->flags & FREC_CHECKING_DISABLED)
header->hb4 |= HB4_CD;
else
header->hb4 &= ~HB4_CD;
} }
#endif #endif
...@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family) ...@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family)
return NULL; /* doom */ return NULL; /* doom */
} }
static void free_frec(struct frec *f) static void free_frec(struct frec *f)
{ {
if (f->rfd4 && --(f->rfd4->refcount) == 0) if (f->rfd4 && --(f->rfd4->refcount) == 0)
...@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f) ...@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f)
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (f->stash) if (f->stash)
blockdata_free(f->stash); {
blockdata_free(f->stash);
f->stash = NULL;
}
/* Anything we're waiting on is pointless now, too */ /* Anything we're waiting on is pointless now, too */
if (f->blocking_query) if (f->blocking_query)
......
...@@ -139,6 +139,7 @@ struct myoption { ...@@ -139,6 +139,7 @@ struct myoption {
#define LOPT_QUIET_DHCP6 327 #define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328 #define LOPT_QUIET_RA 328
#define LOPT_SEC_VALID 329 #define LOPT_SEC_VALID 329
#define LOPT_DNSKEY 330
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
...@@ -276,6 +277,7 @@ static const struct myoption opts[] = ...@@ -276,6 +277,7 @@ static const struct myoption opts[] =
{ "ipset", 1, 0, LOPT_IPSET }, { "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH }, { "synth-domain", 1, 0, LOPT_SYNTH },
{ "dnssec", 0, 0, LOPT_SEC_VALID }, { "dnssec", 0, 0, LOPT_SEC_VALID },
{ "dnskey", 1, 0, LOPT_DNSKEY },
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif #endif
...@@ -428,6 +430,7 @@ static struct { ...@@ -428,6 +430,7 @@ static struct {
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL },
#endif #endif
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
...@@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ...@@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->host_records_tail = new; daemon->host_records_tail = new;
break; break;
} }
#ifdef HAVE_DNSSEC
case LOPT_DNSKEY:
{
struct dnskey *new = opt_malloc(sizeof(struct dnskey));
char *key64, *algo;
if (!(comma = split(arg)) || !(algo = split(comma)) || !(key64 = split(algo)) ||
!atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
!(new->name = canonicalise_opt(arg)))
ret_err(_("bad DNSKEY"));
/* Upper bound on length */
new->key = opt_malloc((3*strlen(key64)/4));
unhide_metas(key64);
if ((new->keylen = parse_base64(key64, new->key)) == -1)
ret_err(_("bad base64 in DNSKEY"));
new->next = daemon->dnskeys;
daemon->dnskeys = new;
break;
}
#endif
default: default:
ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)")); ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
} }
......
...@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned ...@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen; /* Too big */ return plen; /* Too big */
} }
PUTSHORT(optno, p); if (optno != 0)
PUTSHORT(optlen, p); {
memcpy(p, opt, optlen); PUTSHORT(optno, p);
p += optlen; PUTSHORT(optlen, p);
memcpy(p, opt, optlen);
p += optlen;
}
PUTSHORT(p - datap, lenp); PUTSHORT(p - datap, lenp);
return p - (unsigned char *)header; return p - (unsigned char *)header;
...@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) ...@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way. expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */ Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec) char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure)
{ {
unsigned char *p, *p1, *endrr, *namep; unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
...@@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t ...@@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
struct crec *cpp = NULL; struct crec *cpp = NULL;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
unsigned long cttl = ULONG_MAX, attl; unsigned long cttl = ULONG_MAX, attl;
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
if (secure)
flags |= F_DNSSECOK;
namep = p; namep = p;
if (!extract_name(header, qlen, &p, name, 1, 4)) if (!extract_name(header, qlen, &p, name, 1, 4))
...@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int dryrun = 0, sec_reqd = 0; int dryrun = 0, sec_reqd = 0;
int is_sign; int is_sign;
struct crec *crecp; struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0; int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec; struct mx_srv_record *rec;
/* If there is an RFC2671 pseudoheader then it will be overwritten by /* If there is an RFC2671 pseudoheader then it will be overwritten by
...@@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
continue; continue;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_NEG) if (crecp->flags & F_NEG)
{ {
ans = 1; ans = 1;
...@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
break; break;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_CNAME) if (crecp->flags & F_CNAME)
{ {
char *cname_target = cache_get_cname_target(crecp); char *cname_target = cache_get_cname_target(crecp);
...@@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) && if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))) (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
{ {
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
ans = 1; ans = 1;
if (!dryrun) if (!dryrun)
{ {
...@@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* truncation */ /* truncation */
if (trunc) if (trunc)
header->hb3 |= HB3_TC; header->hb3 |= HB3_TC;
header->hb4 &= ~HB4_AD;
if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY))
if (sec_data)
header->hb4 |= HB4_AD;
if (nxdomain) if (nxdomain)
SET_RCODE(header, NXDOMAIN); SET_RCODE(header, NXDOMAIN);
else else
......
...@@ -109,10 +109,10 @@ static int check_name(char *in) ...@@ -109,10 +109,10 @@ static int check_name(char *in)
if (in[l-1] == '.') if (in[l-1] == '.')
{ {
if (l == 1) return 0;
in[l-1] = 0; in[l-1] = 0;
nowhite = 1;
} }
for (; (c = *in); in++) for (; (c = *in); in++)
{ {
if (c == '.') if (c == '.')
...@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen, ...@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
return i; return i;
} }
#ifdef HAVE_DNSSEC
static int charval(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+')
return 62;
if (c == '/')
return 63;
if (c == '=')
return -1;
return -2;
}
int parse_base64(char *in, char *out)
{
char *p = out;
int i, val[4];
while (*in)
{
for (i = 0; i < 4; i++)
{
while (*in == ' ')
in++;
if (*in == 0)
return -1;
if ((val[i] = charval(*in++)) == -2)
return -1;
}
while (*in == ' ')
in++;
if (val[1] == -1)
return -1; /* too much padding */
*p++ = (val[0] << 2) | (val[1] >> 4);
if (val[2] != -1)
*p++ = (val[1] << 4) | ( val[2] >> 2);
if (val[3] != -1)
*p++ = (val[2] << 6) | val[3];
}
return p - out;
}
#endif
/* return 0 for no match, or (no matched octets) + 1 */ /* return 0 for no match, or (no matched octets) + 1 */
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask) int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
{ {
......
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