Index: daemon/acl_list.h
===================================================================
--- daemon/acl_list.h	(revision 1868)
+++ daemon/acl_list.h	(working copy)
@@ -45,6 +45,7 @@
 #include "util/storage/dnstree.h"
 struct config_file;
 struct regional;
+struct config_str2list;
 
 /**
  * Enumeration of access control options for an address range.
@@ -100,10 +101,11 @@
 /**
  * Process access control config.
  * @param acl: where to store.
- * @param cfg: config options.
+ * @param acllist: linked acl list.
  * @return 0 on error.
  */
-int acl_list_apply_cfg(struct acl_list* acl, struct config_file* cfg);
+int acl_list_apply_cfg(struct acl_list* acl, struct config_str2list* acllist, 
+	int do_ip6);
 
 /**
  * Lookup address to see its access control status.
Index: daemon/remote.h
===================================================================
--- daemon/remote.h	(revision 1868)
+++ daemon/remote.h	(working copy)
@@ -133,6 +133,14 @@
 	struct listen_port* ports, struct worker* worker);
 
 /**
+ * Remove all rrsets and keys from zone from cache.
+ * @param ssl: the SSL connection to print to.
+ * @param worker: worker with communication base. and links to command channels.
+ * @param arg: argument to find out and flush.
+ */
+void do_flush_zone(SSL* ssl, struct worker* worker, char* arg);
+
+/**
  * Handle nonthreaded remote cmd execution.
  * @param worker: this worker (the remote worker).
  */
Index: daemon/daemon.c
===================================================================
--- daemon/daemon.c	(revision 1868)
+++ daemon/daemon.c	(working copy)
@@ -201,7 +201,8 @@
 	}
 	alloc_init(&daemon->superalloc, NULL, 0);
 	daemon->acl = acl_list_create();
-	if(!daemon->acl) {
+	daemon->acl_notify = acl_list_create();
+	if(!daemon->acl || !daemon->acl_notify) {
 		free(daemon->env);
 		free(daemon);
 		return NULL;
@@ -416,8 +417,10 @@
 daemon_fork(struct daemon* daemon)
 {
 	log_assert(daemon);
-	if(!acl_list_apply_cfg(daemon->acl, daemon->cfg))
+	if(!acl_list_apply_cfg(daemon->acl, daemon->cfg->acls, daemon->cfg->do_ip6))
 		fatal_exit("Could not setup access control list");
+	if(!acl_list_apply_cfg(daemon->acl_notify, daemon->cfg->acls_notify, daemon->cfg->do_ip6))
+		fatal_exit("Could not setup notify access control list");
 	if(!(daemon->local_zones = local_zones_create()))
 		fatal_exit("Could not create local zones: out of memory");
 	if(!local_zones_apply_cfg(daemon->local_zones, daemon->cfg))
Index: daemon/daemon.h
===================================================================
--- daemon/daemon.h	(revision 1868)
+++ daemon/daemon.h	(working copy)
@@ -96,6 +96,8 @@
 	struct module_stack mods;
 	/** access control, which client IPs are allowed to connect */
 	struct acl_list* acl;
+	/** access control, which client IPs are allowed to notify */
+	struct acl_list* acl_notify;
 	/** local authority zones */
 	struct local_zones* local_zones;
 	/** last time of statistics printout */
Index: daemon/worker.c
===================================================================
--- daemon/worker.c	(revision 1868)
+++ daemon/worker.c	(working copy)
@@ -295,7 +295,8 @@
 		verbose(VERB_QUERY, "request bad, has TC bit on");
 		return LDNS_RCODE_FORMERR;
 	}
-	if(LDNS_OPCODE_WIRE(ldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) {
+	if(LDNS_OPCODE_WIRE(ldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY &&
+		LDNS_OPCODE_WIRE(ldns_buffer_begin(pkt)) != LDNS_PACKET_NOTIFY) {
 		verbose(VERB_QUERY, "request unknown opcode %d", 
 			LDNS_OPCODE_WIRE(ldns_buffer_begin(pkt)));
 		return LDNS_RCODE_NOTIMPL;
@@ -689,6 +690,7 @@
 	struct query_info qinfo;
 	struct edns_data edns;
 	enum acl_access acl;
+	char buf[257];
 
 	if(error != NETEVENT_NOERROR) {
 		/* some bad tcp query DNS formats give these error calls */
@@ -727,7 +729,6 @@
 		comm_point_drop_reply(repinfo);
 		return 0;
 	}
-	worker->stats.num_queries++;
 	/* see if query is in the cache */
 	if(!query_info_parse(&qinfo, c->buffer)) {
 		verbose(VERB_ALGO, "worker parse request: formerror.");
@@ -739,6 +740,32 @@
 		server_stats_insrcode(&worker->stats, c->buffer);
 		return 1;
 	}
+	if(LDNS_OPCODE_WIRE(ldns_buffer_begin(c->buffer)) == LDNS_PACKET_NOTIFY) {
+		acl = acl_list_lookup(worker->daemon->acl_notify, &repinfo->addr, 
+			repinfo->addrlen);
+		if(acl == acl_deny) {
+			comm_point_drop_reply(repinfo);
+			return 0;
+		} else if(acl == acl_refuse) {
+			ldns_buffer_rewind(c->buffer);
+			LDNS_QR_SET(ldns_buffer_begin(c->buffer));
+			LDNS_RCODE_SET(ldns_buffer_begin(c->buffer), 
+				LDNS_RCODE_REFUSED);
+			log_addr(VERB_ALGO, "refused notify from",
+				&repinfo->addr, repinfo->addrlen);
+			log_buf(VERB_ALGO, "refuse", c->buffer);
+			return 1;
+		}
+		verbose(VERB_ALGO, "received notify.");
+		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
+		dname_str(qinfo.qname, buf);
+		do_flush_zone(NULL, worker, buf);
+		LDNS_QR_SET(ldns_buffer_begin(c->buffer));
+		ldns_buffer_rewind(c->buffer);
+		comm_point_send_reply(repinfo);
+		return 0;
+	}
+	worker->stats.num_queries++;
 	if(qinfo.qtype == LDNS_RR_TYPE_AXFR || 
 		qinfo.qtype == LDNS_RR_TYPE_IXFR) {
 		verbose(VERB_ALGO, "worker request: refused zone transfer.");
Index: daemon/acl_list.c
===================================================================
--- daemon/acl_list.c	(revision 1868)
+++ daemon/acl_list.c	(working copy)
@@ -123,10 +123,10 @@
 
 /** read acl_list config */
 static int 
-read_acl_list(struct acl_list* acl, struct config_file* cfg)
+read_acl_list(struct acl_list* acl, struct config_str2list* acllist)
 {
 	struct config_str2list* p;
-	for(p = cfg->acls; p; p = p->next) {
+	for(p = acllist; p; p = p->next) {
 		log_assert(p->str && p->str2);
 		if(!acl_list_str_cfg(acl, p->str, p->str2, 1))
 			return 0;
@@ -135,18 +135,18 @@
 }
 
 int 
-acl_list_apply_cfg(struct acl_list* acl, struct config_file* cfg)
+acl_list_apply_cfg(struct acl_list* acl, struct config_str2list* acllist, int do_ip6)
 {
 	regional_free_all(acl->region);
 	addr_tree_init(&acl->tree);
-	if(!read_acl_list(acl, cfg))
+	if(!read_acl_list(acl, acllist))
 		return 0;
 	/* insert defaults, with '0' to ignore them if they are duplicates */
 	if(!acl_list_str_cfg(acl, "0.0.0.0/0", "refuse", 0))
 		return 0;
 	if(!acl_list_str_cfg(acl, "127.0.0.0/8", "allow", 0))
 		return 0;
-	if(cfg->do_ip6) {
+	if(do_ip6) {
 		if(!acl_list_str_cfg(acl, "::0/0", "refuse", 0))
 			return 0;
 		if(!acl_list_str_cfg(acl, "::1", "allow", 0))
Index: daemon/remote.c
===================================================================
--- daemon/remote.c	(revision 1868)
+++ daemon/remote.c	(working copy)
@@ -1161,7 +1161,7 @@
 }
 
 /** remove all rrsets and keys from zone from cache */
-static void
+void
 do_flush_zone(SSL* ssl, struct worker* worker, char* arg)
 {
 	uint8_t* nm;
Index: util/config_file.c
===================================================================
--- util/config_file.c	(revision 1868)
+++ util/config_file.c	(working copy)
@@ -131,6 +131,7 @@
 	cfg->stubs = NULL;
 	cfg->forwards = NULL;
 	cfg->acls = NULL;
+	cfg->acls_notify = NULL;
 	cfg->harden_short_bufsize = 0;
 	cfg->harden_large_queries = 0;
 	cfg->harden_glue = 1;
@@ -564,6 +565,7 @@
 	free(cfg->dlv_anchor_file);
 	config_delstrlist(cfg->dlv_anchor_list);
 	config_deldblstrlist(cfg->acls);
+	config_deldblstrlist(cfg->acls_notify);
 	free(cfg->val_nsec3_key_iterations);
 	config_deldblstrlist(cfg->local_zones);
 	config_delstrlist(cfg->local_zones_nodefault);
Index: util/configparser.y
===================================================================
--- util/configparser.y	(revision 1868)
+++ util/configparser.y	(working copy)
@@ -87,9 +87,9 @@
 %token VAR_VAL_NSEC3_KEYSIZE_ITERATIONS VAR_USE_SYSLOG 
 %token VAR_OUTGOING_INTERFACE VAR_ROOT_HINTS VAR_DO_NOT_QUERY_LOCALHOST
 %token VAR_CACHE_MAX_TTL VAR_HARDEN_DNNSEC_STRIPPED VAR_ACCESS_CONTROL
-%token VAR_LOCAL_ZONE VAR_LOCAL_DATA VAR_INTERFACE_AUTOMATIC
-%token VAR_STATISTICS_INTERVAL VAR_DO_DAEMONIZE VAR_USE_CAPS_FOR_ID
-%token VAR_STATISTICS_CUMULATIVE VAR_OUTGOING_PORT_PERMIT 
+%token VAR_ACCESS_CONTROL_NOTIFY VAR_LOCAL_ZONE VAR_LOCAL_DATA
+%token VAR_INTERFACE_AUTOMATIC VAR_STATISTICS_INTERVAL VAR_DO_DAEMONIZE
+%token VAR_USE_CAPS_FOR_ID VAR_STATISTICS_CUMULATIVE VAR_OUTGOING_PORT_PERMIT
 %token VAR_OUTGOING_PORT_AVOID VAR_DLV_ANCHOR_FILE VAR_DLV_ANCHOR
 %token VAR_NEG_CACHE_SIZE VAR_HARDEN_REFERRAL_PATH VAR_PRIVATE_ADDRESS
 %token VAR_PRIVATE_DOMAIN VAR_REMOTE_CONTROL VAR_CONTROL_ENABLE
@@ -140,7 +140,8 @@
 	server_use_syslog | server_outgoing_interface | server_root_hints |
 	server_do_not_query_localhost | server_cache_max_ttl |
 	server_harden_dnssec_stripped | server_access_control |
-	server_local_zone | server_local_data | server_interface_automatic |
+	server_access_control_notify | server_local_zone | 
+	server_local_data | server_interface_automatic |
 	server_statistics_interval | server_do_daemonize | 
 	server_use_caps_for_id | server_statistics_cumulative |
 	server_outgoing_port_permit | server_outgoing_port_avoid |
@@ -768,6 +769,19 @@
 		}
 	}
 	;
+server_access_control_notify: VAR_ACCESS_CONTROL_NOTIFY STRING_ARG STRING_ARG
+	{
+		OUTYY(("P(server_access_control_notify:%s %s)\n", $2, $3));
+		if(strcmp($3, "deny")!=0 && strcmp($3, "refuse")!=0 &&
+			strcmp($3, "allow")!=0) {
+			yyerror("expected deny, refuse or allow "
+				"in access control action");
+		} else {
+			if(!cfg_str2list_insert(&cfg_parser->cfg->acls_notify, $2, $3))
+				fatal_exit("out of memory adding acl");
+		}
+	}
+	;
 server_module_conf: VAR_MODULE_CONF STRING_ARG
 	{
 		OUTYY(("P(server_module_conf:%s)\n", $2));
Index: util/config_file.h
===================================================================
--- util/config_file.h	(revision 1868)
+++ util/config_file.h	(working copy)
@@ -138,6 +138,8 @@
 	struct config_strlist* donotqueryaddrs;
 	/** list of access control entries, linked list */
 	struct config_str2list* acls;
+	/** list of notify access control entries, linked list */
+	struct config_str2list* acls_notify;
 	/** use default localhost donotqueryaddr entries */
 	int donotquery_localhost;
 
Index: util/data/msgreply.c
===================================================================
--- util/data/msgreply.c	(revision 1868)
+++ util/data/msgreply.c	(working copy)
@@ -498,7 +498,8 @@
 	/* minimum size: header + \0 + qtype + qclass */
 	if(ldns_buffer_limit(query) < LDNS_HEADER_SIZE + 5)
 		return 0;
-	if(LDNS_OPCODE_WIRE(q) != LDNS_PACKET_QUERY || 
+	if((LDNS_OPCODE_WIRE(q) != LDNS_PACKET_QUERY && 
+		LDNS_OPCODE_WIRE(q) != LDNS_PACKET_NOTIFY) ||
 		LDNS_QDCOUNT(q) != 1 || ldns_buffer_position(query) != 0)
 		return 0;
 	ldns_buffer_skip(query, LDNS_HEADER_SIZE);
Index: util/configlexer.lex
===================================================================
--- util/configlexer.lex	(revision 1868)
+++ util/configlexer.lex	(working copy)
@@ -184,6 +184,7 @@
 do-not-query-address{COLON}	{ YDVAR(1, VAR_DO_NOT_QUERY_ADDRESS) }
 do-not-query-localhost{COLON}	{ YDVAR(1, VAR_DO_NOT_QUERY_LOCALHOST) }
 access-control{COLON}		{ YDVAR(2, VAR_ACCESS_CONTROL) }
+access-control-notify{COLON}	{ YDVAR(2, VAR_ACCESS_CONTROL_NOTIFY) }
 hide-identity{COLON}		{ YDVAR(1, VAR_HIDE_IDENTITY) }
 hide-version{COLON}		{ YDVAR(1, VAR_HIDE_VERSION) }
 identity{COLON}			{ YDVAR(1, VAR_IDENTITY) }
