diff --git a/NEWS b/NEWS
index 59292e553c701a43a8381905f0c49eb4e689a34b..726c32a0ac5e0a457d92c83057d4d16acb207c89 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,12 @@
+Knot DNS 1.5.2 (2014-09-08)
+==========================
+
+Bugfixes:
+---------
+ - Some RR parsing corner cases were not handled properly
+ - AXFR-style IXFR was refused and had to be retransfered
+ - Hash character (#) was not properly escaped when storing text zone file
+
 Knot DNS 1.5.1 (2014-08-19)
 ===========================
 
diff --git a/configure.ac b/configure.ac
index 1b7afd8114781bd8df9c262984f12ddc58c7039c..8477232706cde804e8722c9e57c471745534e5ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
 #                                               -*- Autoconf -*-
 
 AC_PREREQ([2.60])
-AC_INIT([knot], [1.5.1], [knot-dns@labs.nic.cz])
+AC_INIT([knot], [1.5.2], [knot-dns@labs.nic.cz])
 AM_INIT_AUTOMAKE([gnits subdir-objects dist-xz -Wall -Werror])
 AM_SILENT_RULES([yes])
 AC_CONFIG_SRCDIR([src/knot/main.c])
diff --git a/m4/code-coverage.m4 b/m4/code-coverage.m4
index 6df6a00aafd1cbc5c5516c0728b620876e44af10..57a3a6e1509353bc2d96845dc5052b9d8578e9b7 100644
--- a/m4/code-coverage.m4
+++ b/m4/code-coverage.m4
@@ -31,7 +31,7 @@ AC_DEFUN([AX_CODE_COVERAGE], [
 		AC_CHECK_PROG([LCOV], [lcov], [lcov])
 		AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])		
 
-		lcov_version_list="1.6 1.7 1.8 1.9 1.10"
+		lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11"
 
 		AS_IF([test "$LCOV"], [
 			AC_CACHE_CHECK([for lcov version], ac_cv_lvoc_version, [
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index bcbbd9525cc9e1f43d2676998e1eded676e8382c..f3c7a338e3fab34d51b5ae11000c062bca5084a8 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -618,7 +618,6 @@ int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
 	int ret = KNOT_EOK;
 	struct timeval now = {0};
 	struct ixfr_proc *ixfr = (struct ixfr_proc*)qdata->ext;
-	knot_pkt_t *query = qdata->query;
 
 	/* If IXFR is disabled, respond with SOA. */
 	if (qdata->param->proc_flags & NS_QUERY_NO_IXFR) {
@@ -641,10 +640,6 @@ int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
 		case KNOT_ERANGE:   /* No history -> AXFR. */
 		case KNOT_ENOENT:
 			IXFROUT_LOG(LOG_INFO, "incomplete history, fallback to AXFR");
-			knot_pkt_clear(pkt);
-			knot_pkt_put_question(pkt, knot_pkt_qname(query),
-			                      knot_pkt_qclass(query),
-			                      KNOT_RRTYPE_AXFR);
 			qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */
 			return axfr_query_process(pkt, qdata);
 		default:            /* Server errors. */
@@ -684,14 +679,17 @@ int ixfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata)
 		return NS_PROC_FAIL;
 	}
 	
-	if (!ixfr_enough_data(pkt)) {
-		return NS_PROC_FAIL;
-	}
+	if (adata->ext == NULL) {
+		if (!ixfr_enough_data(pkt)) {
+			return NS_PROC_FAIL;
+		}
 	
-	/* Check for AXFR-style IXFR. */
-	if (ixfr_is_axfr(pkt)) {
-		IXFRIN_LOG(LOG_NOTICE, "fallback to AXFR");
-		return axfr_answer_process(pkt, adata);
+		/* Check for AXFR-style IXFR. */
+		if (ixfr_is_axfr(pkt)) {
+			IXFRIN_LOG(LOG_NOTICE, "receiving AXFR-style IXFR");
+			adata->response_type = KNOT_RESPONSE_AXFR;
+			return axfr_answer_process(pkt, adata);
+		}
 	}
 	
 	/* Check RCODE. */
diff --git a/src/knot/nameserver/process_answer.c b/src/knot/nameserver/process_answer.c
index 521d2dcb0f61ff702fc4527145ddb98b460579e0..e18916b1a180ca7a0ada5a88f3bdf663b65e6e77 100644
--- a/src/knot/nameserver/process_answer.c
+++ b/src/knot/nameserver/process_answer.c
@@ -22,12 +22,14 @@
 
 /*! \brief Accessor to query-specific data. */
 #define ANSWER_DATA(ctx) ((struct answer_data *)(ctx)->data)
+#define RESPONSE_TYPE_UNSET -1
 
 static void answer_data_init(knot_process_t *ctx, void *module_param)
 {
 	/* Initialize persistent data. */
 	struct answer_data *data = ANSWER_DATA(ctx);
 	memset(data, 0, sizeof(struct answer_data));
+	data->response_type = RESPONSE_TYPE_UNSET;
 	data->mm = &ctx->mm;
 	data->param = module_param;
 }
@@ -119,9 +121,11 @@ static int process_answer(knot_pkt_t *pkt, knot_process_t *ctx)
 
 	/* Call appropriate processing handler. */
 	int next_state = NS_PROC_NOOP;
-	int response_type = knot_pkt_type(query) | KNOT_RESPONSE;
-	/* @note We can't derive type from response, as it may not contain QUESTION at all. */
-	switch(response_type) {
+	if (data->response_type == RESPONSE_TYPE_UNSET) {
+		/* @note We can't derive type from response, as it may not contain QUESTION at all. */
+		data->response_type = knot_pkt_type(query) | KNOT_RESPONSE;
+	}
+	switch(data->response_type) {
 	case KNOT_RESPONSE_NORMAL:
 		next_state = internet_process_answer(pkt, data);
 		break;
diff --git a/src/knot/nameserver/process_answer.h b/src/knot/nameserver/process_answer.h
index 571edc82d0602020a86179c7e8104ab076376e8c..e87ea57b5bd52bc8dc54cb417b2b083e4787e424 100644
--- a/src/knot/nameserver/process_answer.h
+++ b/src/knot/nameserver/process_answer.h
@@ -59,6 +59,7 @@ struct answer_data {
 	knot_sign_context_t sign;            /*!< Signing context. */
 
 	/* Everything below should be kept on reset. */
+	int response_type; /*!< Type of incoming response. */
 	struct process_answer_param *param; /*!< Module parameters. */
 	mm_ctx_t *mm;                      /*!< Memory context. */
 };
diff --git a/src/knot/zone/events.c b/src/knot/zone/events.c
index 92d1d7a5233b4da92d414eebc7f642e87f1b5818..1235dad54ece6e11f080903923fdad325a3e891b 100644
--- a/src/knot/zone/events.c
+++ b/src/knot/zone/events.c
@@ -403,11 +403,11 @@ static int event_update(zone_t *zone)
 	mem_trim();
 
 	/* Replan event if next update waiting. */
-	pthread_spin_lock(&zone->ddns_lock);
+	pthread_mutex_lock(&zone->ddns_lock);
 	
 	const bool empty = EMPTY_LIST(zone->ddns_queue);
 	
-	pthread_spin_unlock(&zone->ddns_lock);
+	pthread_mutex_unlock(&zone->ddns_lock);
 	
 	if (!empty) {
 		zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
@@ -641,14 +641,14 @@ static void duplicate_ddns_q(zone_t *zone, zone_t *old_zone)
 /*!< Replans DDNS event. */
 static void replan_update(zone_t *zone, zone_t *old_zone)
 {
-	pthread_spin_lock(&old_zone->ddns_lock);
+	pthread_mutex_lock(&old_zone->ddns_lock);
 
 	const bool have_updates = old_zone->ddns_queue_size > 0;
 	if (have_updates) {
 		duplicate_ddns_q(zone, (zone_t *)old_zone);
 	}
 	
-	pthread_spin_unlock(&old_zone->ddns_lock);
+	pthread_mutex_unlock(&old_zone->ddns_lock);
 	
 	if (have_updates) {
 		zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 68e27c8acc2a23952881a7b4047c3be33e59add5..e3557ebbba4eef651c8da7f2c4544d8c59e05b6e 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -72,7 +72,7 @@ zone_t* zone_new(conf_zone_t *conf)
 	zone->conf = conf;
 
 	// DDNS
-	pthread_spin_init(&zone->ddns_lock, 0);
+	pthread_mutex_init(&zone->ddns_lock, NULL);
 	zone->ddns_queue_size = 0;
 	init_list(&zone->ddns_queue);
 
@@ -95,7 +95,7 @@ void zone_free(zone_t **zone_ptr)
 	knot_dname_free(&zone->name, NULL);
 
 	free_ddns_queue(zone);
-	pthread_spin_destroy(&zone->ddns_lock);
+	pthread_mutex_destroy(&zone->ddns_lock);
 
 	/* Free assigned config. */
 	conf_free_zone(zone->conf);
@@ -266,13 +266,13 @@ int zone_update_enqueue(zone_t *zone, knot_pkt_t *pkt, struct process_query_para
 		return ret;
 	}
 
-	pthread_spin_lock(&zone->ddns_lock);
+	pthread_mutex_lock(&zone->ddns_lock);
 
 	/* Enqueue created request. */
 	add_tail(&zone->ddns_queue, (node_t *)req);
 	++zone->ddns_queue_size;
 
-	pthread_spin_unlock(&zone->ddns_lock);
+	pthread_mutex_unlock(&zone->ddns_lock);
 
 	/* Schedule UPDATE event. */
 	zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
@@ -286,10 +286,10 @@ size_t zone_update_dequeue(zone_t *zone, list_t *updates)
 		return 0;
 	}
 
-	pthread_spin_lock(&zone->ddns_lock);
+	pthread_mutex_lock(&zone->ddns_lock);
 	if (EMPTY_LIST(zone->ddns_queue)) {
 		/* Lost race during reload. */
-		pthread_spin_unlock(&zone->ddns_lock);
+		pthread_mutex_unlock(&zone->ddns_lock);
 		return 0;
 	}
 
@@ -298,7 +298,7 @@ size_t zone_update_dequeue(zone_t *zone, list_t *updates)
 	init_list(&zone->ddns_queue);
 	zone->ddns_queue_size = 0;
 
-	pthread_spin_unlock(&zone->ddns_lock);
+	pthread_mutex_unlock(&zone->ddns_lock);
 	
 	return update_count;
 }
diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h
index 83c94e48ecb0b82384579e4cfa51c7b7209e4e68..50d0a9ccdfbb40d0f049d1a24e0bf235bb815ce3 100644
--- a/src/knot/zone/zone.h
+++ b/src/knot/zone/zone.h
@@ -58,7 +58,7 @@ typedef struct zone_t
 	zone_flag_t flags;
 
 	/*! \brief DDNS queue and lock. */
-	pthread_spinlock_t ddns_lock;
+	pthread_mutex_t ddns_lock;
 	size_t ddns_queue_size;
 	list_t ddns_queue;
 
diff --git a/tests-extra/requirements.txt b/tests-extra/requirements.txt
index a42bc44e35e7983531bd460bffa835332b93d5dc..56782b1df8edf425e1da4e4635e2e8f9306c69f5 100644
--- a/tests-extra/requirements.txt
+++ b/tests-extra/requirements.txt
@@ -1,3 +1,3 @@
-#dnspython3==1.11.1
--e git+https://github.com/rthalley/dnspython.git@python3#egg=dnspython3
+#-e git+https://github.com/rthalley/dnspython.git@python3#egg=dnspython3
+dnspython3==1.12.0
 psutil==1.2.1
diff --git a/tests-extra/tests/ixfr/axfr_style/data/xfr.zone b/tests-extra/tests/ixfr/axfr_style/data/xfr.zone
new file mode 100644
index 0000000000000000000000000000000000000000..afec07e72f6c30ad377005c1c1b785983ef87d4a
--- /dev/null
+++ b/tests-extra/tests/ixfr/axfr_style/data/xfr.zone
@@ -0,0 +1,3 @@
+xfr. SOA . . 1 100 100 100 1000
+xfr. IN NS a
+a IN A 1.2.3.4
diff --git a/tests-extra/tests/ixfr/axfr_style/data/xfr.zone.1 b/tests-extra/tests/ixfr/axfr_style/data/xfr.zone.1
new file mode 100644
index 0000000000000000000000000000000000000000..bebb90afd7472a80550c61a92456a4c1ee417861
--- /dev/null
+++ b/tests-extra/tests/ixfr/axfr_style/data/xfr.zone.1
@@ -0,0 +1,4 @@
+xfr. SOA . . 2 100 100 100 100
+xfr. IN NS a
+a IN A 1.2.3.4
+test TXT "test"
diff --git a/tests-extra/tests/ixfr/axfr_style/test.py b/tests-extra/tests/ixfr/axfr_style/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c38fec0193a606e57dca84860ddfdb60c102fd0
--- /dev/null
+++ b/tests-extra/tests/ixfr/axfr_style/test.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+'''Test for AXFR-style IXFR'''
+
+from dnstest.test import Test
+import os
+
+t = Test(tsig=False)
+
+master = t.server("bind")
+slave = t.server("knot")
+zone = t.zone("xfr", storage=".")
+
+t.link(zone, master, slave)
+
+t.start()
+
+serial = master.zone_wait(zone)
+
+# update zone with small change
+master.update_zonefile(zone, version=1)
+master.reload()
+master.zone_wait(zone, serial)
+serial = slave.zone_wait(zone, serial)
+
+t.xfr_diff(master, slave, zone)
+
+# update zone with large change (3 messages worth)
+filename = master.data_dir + zone[0].name + "zone.2"
+f = open(filename, "w")
+f.write(zone[0].name + " IN SOA . . 3 100 100 100 100\n")
+f.write(zone[0].name + " IN NS smth\n")
+f.write("smth IN A 1.2.3.4\n")
+for i in range(2000):
+    f.write("test" + str(i) + " TXT " + "test" * 16 + "\n")
+f.close()
+master.update_zonefile(zone, version=2)
+os.remove(filename)
+
+master.reload()
+master.zone_wait(zone, serial)
+slave.zone_wait(zone, serial)
+t.xfr_diff(master, slave, zone)
+
+t.stop()
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index 61110b14a70fd82018a6c5d630ac881791111c43..1b3e86a018cffb3e4047eb9a70d8df08e364969f 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -660,7 +660,8 @@ class Server(object):
         if random:
             self.zones[zone.name].zfile.update_rnd()
         else:
-            self.zones[zone.name].zfile.upd_file(version=version)
+            self.zones[zone.name].zfile.upd_file(storage=self.data_dir,
+                                                 version=version)
 
     def add_query_module(self, zone, module, param):
         # Convert one item list to single object.
diff --git a/tests-extra/tools/dnstest/zonefile.py b/tests-extra/tools/dnstest/zonefile.py
index da7c60aed82a8d4bb97701143e463c807866ac11..19c6f45e374ac055bfbc1174ccc25c16687f1bde 100644
--- a/tests-extra/tools/dnstest/zonefile.py
+++ b/tests-extra/tools/dnstest/zonefile.py
@@ -20,7 +20,6 @@ class ZoneFile(object):
         # Directory containing source zone file/updates.
         self.storage = None
 
-
         self.backup_num = 1
 
     @property
@@ -47,12 +46,11 @@ class ZoneFile(object):
 
         if not file_name:
             file_name = self.name + "zone"
-
-        if storage:
-            self.storage = storage
-
         self.file_name = os.path.basename(file_name)
 
+        if not self.storage:
+            self.storage = storage if storage else os.path.dirname(file_name)
+
         if not exists:
             return
 
@@ -66,6 +64,19 @@ class ZoneFile(object):
                 src_file += "." + str(version)
 
             shutil.copyfile(src_file, self.path)
+
+            # Copy zone keys.
+            keydir = self.storage + "/keys"
+            if os.path.isdir(keydir):
+                prepare_dir(self.key_dir)
+
+                src_files = os.listdir(keydir)
+                for file_name in src_files:
+                    if (self.name[:-1]).lower() in file_name:
+                        full_file_name = os.path.join(keydir, file_name)
+                        if (os.path.isfile(full_file_name)):
+                            shutil.copy(full_file_name, self.key_dir)
+
         except:
             raise Exception("Can't use zone file '%s'" % src_file)
 
diff --git a/tests/rrset_wire.c b/tests/rrset_wire.c
index 57ae73d0638e16fd5c8cf7161cb78936842960d9..11ce6a9179dac0707abe8a433a152e7e03ec7a61 100644
--- a/tests/rrset_wire.c
+++ b/tests/rrset_wire.c
@@ -73,7 +73,7 @@ struct wire_data {
 	const char *msg;
 };
 
-#define FROM_CASE_COUNT 16
+#define FROM_CASE_COUNT 17
 
 static const struct wire_data FROM_CASES[FROM_CASE_COUNT] = {
 { .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A)},
@@ -155,6 +155,13 @@ static const struct wire_data FROM_CASES[FROM_CASE_COUNT] = {
   .pos = QUERY_SIZE + QNAME_SIZE,
   .code = KNOT_EMALF,
   .msg = "NAPTR bad offset"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
+            RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x09),
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+  .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 7,
+  .pos = QUERY_SIZE + QNAME_SIZE,
+  .code = KNOT_EMALF,
+  .msg = "NAPTR no DNAME"},
 { .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
             RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x0c),
             0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0xff, 0x01, 0xff, QNAME_POINTER},