diff -ru milter-greylist.maybeforged/acl.c milter-greylist.thread/acl.c
--- milter-greylist.maybeforged/acl.c	2006-08-21 05:10:43.000000000 +0000
+++ milter-greylist.thread/acl.c	2006-08-21 13:12:41.000000000 +0000
@@ -1094,10 +1094,10 @@
 	 */
 	debug = fopen("/tmp/access-list.debug", "w");
 	ACL_RDLOCK;
-	syslog(LOG_INFO, "Access list dump:\n");
+	syslog(LOG_DEBUG, "Access list dump:");
 	TAILQ_FOREACH(acl, &acl_head, a_list) {
 		entry = acl_entry(acl);
-		syslog(LOG_INFO, "%s\n", entry);
+		syslog(LOG_DEBUG, "%s", entry);
 		if (debug != NULL)
 			fprintf (debug, "%s\n", entry);
 	}
diff -ru milter-greylist.maybeforged/autowhite.c milter-greylist.thread/autowhite.c
--- milter-greylist.maybeforged/autowhite.c	2006-08-20 05:53:25.000000000 +0000
+++ milter-greylist.thread/autowhite.c	2006-08-21 12:42:39.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: autowhite.c,v 1.49 2006/08/20 05:53:25 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -65,14 +66,15 @@
 #include "acl.h"
 #include "sync.h"
 
+static void autowhite_insert_to_queue(struct autowhite *);
+
+pthread_mutex_t autowhite_lock = PTHREAD_MUTEX_INITIALIZER;
 struct autowhitelist autowhite_head;
 struct autowhite_bucket *autowhite_buckets;
-pthread_rwlock_t autowhite_lock;
-pthread_mutex_t autowhite_change_lock;
 
 void
 autowhite_init(void) {
-	int error, i;
+	int i;
 
 	TAILQ_INIT(&autowhite_head);
 	if ((autowhite_buckets = calloc(AUTOWHITE_BUCKETS, 
@@ -82,25 +84,9 @@
 		    strerror(errno));
 		exit(EX_OSERR);
 	}
-	
-	if ((error = pthread_rwlock_init(&autowhite_lock, NULL)) != 0 ||
-	    (error = pthread_mutex_init(&autowhite_change_lock, NULL)) != 0) {
-		syslog(LOG_ERR, "pthread_rwlock_init failed: %s",
-		    strerror(error));
-		    exit(EX_OSERR);
-	}	
 
 	for(i = 0; i < AUTOWHITE_BUCKETS; i++) {
 		TAILQ_INIT(&autowhite_buckets[i].b_autowhite_head);
-		
-		if ((error = 
-		    pthread_mutex_init(&autowhite_buckets[i].bucket_mtx, 
-		    NULL)) != 0) {
-			syslog(LOG_ERR, 
-			    "pthread_mutex_init failed: %s", strerror(error));
-			exit(EX_OSERR);
-		}
-		
 	}
 
 	return;
@@ -116,7 +102,7 @@
 	
 	gettimeofday(&now, NULL);
 	
-	AUTOWHITE_WRLOCK;
+	AUTOWHITE_LOCK;
 	for (aw = TAILQ_FIRST(&autowhite_head); aw; aw = next_aw) {
 		next_aw = TAILQ_NEXT(aw, a_list);
 		
@@ -154,7 +140,6 @@
 	char *queueid;
 {
 	struct autowhite *aw;
-	struct autowhite *next_aw;
 	struct timeval now;
 	char addr[IPADDRSTRLEN];
 	int h, mn, s;
@@ -186,30 +171,9 @@
 
 	dirty = autowhite_timeout();
 	
-	AUTOWHITE_RDLOCK;
+	AUTOWHITE_LOCK;
 	b = &autowhite_buckets[BUCKET_HASH(sa, from, rcpt, AUTOWHITE_BUCKETS)];
-	pthread_mutex_lock(&b->bucket_mtx);
-	for (aw = TAILQ_FIRST(&b->b_autowhite_head); aw; aw = next_aw) {
-		next_aw = TAILQ_NEXT(aw, ab_list);
-
-		/*
-		 * Expiration (left this one in too until the list gets sorted)
-		 */
-		if (aw->a_tv.tv_sec < now.tv_sec) {
-			char buf[IPADDRSTRLEN];
-
-			iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
-                      syslog(LOG_INFO, "(local): addr %s from %s rcpt %s: "
-			    "autowhitelisted entry expired",
-			    buf, aw->a_from, aw->a_rcpt);
-
-			autowhite_put(aw);
-
-			dirty++;
-
-			continue;
-		}
-
+	TAILQ_FOREACH(aw, &b->b_autowhite_head, ab_list) {
 	 	/*
 		 * Look for an already existing entry
 		 */
@@ -219,11 +183,9 @@
 		    (strcasecmp(rcpt, aw->a_rcpt) == 0)))) {
 			aw->a_tv.tv_sec = *date;
 
-			/* Push it at the back of the big queue */
-			pthread_mutex_lock(&autowhite_change_lock);
+			/* Rearrange the big queue */
 			TAILQ_REMOVE(&autowhite_head, aw, a_list);
-			TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
-			pthread_mutex_unlock(&autowhite_change_lock);
+			autowhite_insert_to_queue(aw);
 
 			dirty++;
 
@@ -246,11 +208,9 @@
 		    "autowhitelisted for %02d:%02d:%02d", 
 		    queueid, addr, from, rcpt, h, mn, s);
 	}
-	pthread_mutex_unlock(&b->bucket_mtx);
 	AUTOWHITE_UNLOCK;
 
-	if (dirty != 0)
-		dump_dirty += dirty;
+	dump_touch(dirty);
 
 	return;
 }
@@ -266,7 +226,6 @@
 	time_t autowhite;
 {
 	struct autowhite *aw;
-	struct autowhite *next_aw;
 	struct pending *pending;
 	struct timeval now, delay;
 	char addr[IPADDRSTRLEN];
@@ -302,34 +261,9 @@
 
 	dirty = autowhite_timeout();
 	
-	AUTOWHITE_RDLOCK;
+	AUTOWHITE_LOCK;
 	b = &autowhite_buckets[BUCKET_HASH(sa, from, rcpt, AUTOWHITE_BUCKETS)];
-	pthread_mutex_lock(&b->bucket_mtx);
-	for (aw = TAILQ_FIRST(&b->b_autowhite_head); aw; aw = next_aw) {
-		next_aw = TAILQ_NEXT(aw, ab_list);
-
-		/*
-		 * Do expiration first as we don't want
-		 * an outdated record to match
-		 * I've left this one too until the lists
-		 * gets sorted
-		 */
-		if (aw->a_tv.tv_sec < now.tv_sec) {
-			char buf[IPADDRSTRLEN];
-
-			iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
-                      syslog(LOG_INFO, "(local): addr %s from %s rcpt %s: "
-			    "autowhitelisted entry expired",
-			    buf, aw->a_from, aw->a_rcpt);
-
-			autowhite_put(aw);
-			aw = NULL;
-
-			dirty++;
-
-			continue;
-		}
-
+	TAILQ_FOREACH(aw, &b->b_autowhite_head, ab_list) {
 		/*
 		 * Look for our record
 		 */
@@ -339,22 +273,19 @@
 		    (strcasecmp(rcpt, aw->a_rcpt) == 0)))) {
 			timeradd(&now, &delay, &aw->a_tv);
 
-			/* Push it at the back of the big queue */
-			pthread_mutex_lock(&autowhite_change_lock);
+			/* Rearrange the big queue */
 			TAILQ_REMOVE(&autowhite_head, aw, a_list);
-			TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
-			pthread_mutex_unlock(&autowhite_change_lock);
+			autowhite_insert_to_queue(aw);
 
 			dirty++;
 
 			break;
 		}
 	}
-	pthread_mutex_unlock(&b->bucket_mtx);
 	AUTOWHITE_UNLOCK;
 
-	if (dirty != 0)
-		dump_dirty += dirty;
+	dump_touch(dirty);
+	dirty = 0;
 
 	if (aw == NULL) 
 		return EXF_NONE;
@@ -366,13 +297,16 @@
 	 * We need to tell our peers about this, we use a
 	 * fictive pending record
 	 */
-	PENDING_WRLOCK;
+	PENDING_LOCK;
 	pending = pending_get(sa, salen, from, rcpt, now.tv_sec + gldelay);
 	if (pending != NULL) {
 		peer_delete(pending, now.tv_sec + autowhite);
 		pending_put(pending);
+		++dirty;
 	}
 	PENDING_UNLOCK;
+	dump_touch(dirty);
+
 	return EXF_AUTO;	
 }
 
@@ -390,8 +324,7 @@
 	fprintf(stream, "# Sender IP\t%s\t%s\tExpire\n",
 	    "Sender e-mail", "Recipient e-mail");
 
-	AUTOWHITE_RDLOCK;
-	pthread_mutex_lock(&autowhite_change_lock);
+	AUTOWHITE_LOCK;
 	TAILQ_FOREACH(aw, &autowhite_head, a_list) {
 		iptostring(aw->a_sa, aw->a_salen, textaddr, sizeof(textaddr));
 	
@@ -412,12 +345,40 @@
 
 		done++;
 	}
-	pthread_mutex_unlock(&autowhite_change_lock);
 	AUTOWHITE_UNLOCK;
 
 	return done;
 }
 
+/*
+ * A new entry is inserted to the back of the queue in most cases,
+ * but it is not true in these situations:
+ * - The conf.c_autowhite_validity was shortened
+ * - System clock was turned to the past
+ *
+ * To ensure that the queue is sorted by expiration times (a_tv),
+ * we need to find the right position where to insert a new entry.
+ */
+
+/* autowhite list must be locked */
+static void
+autowhite_insert_to_queue(newentry)
+	struct autowhite *newentry;
+{
+	struct autowhite *aw;
+	
+	TAILQ_FOREACH_REVERSE(aw, &autowhite_head, autowhitelist, a_list) {
+		if (aw->a_tv.tv_sec < newentry->a_tv.tv_sec ||
+		    (aw->a_tv.tv_sec == newentry->a_tv.tv_sec &&
+		     aw->a_tv.tv_usec < newentry->a_tv.tv_usec))
+			break;
+	}
+	if (aw)
+		TAILQ_INSERT_AFTER(&autowhite_head, aw, newentry, a_list);
+	else
+		TAILQ_INSERT_HEAD(&autowhite_head, newentry, a_list);
+}
+
 struct autowhite *
 autowhite_get(sa, salen, from, rcpt, date) /* autowhite list must be locked */
 	struct sockaddr *sa;
@@ -446,25 +407,21 @@
 	memcpy(aw->a_sa, sa, salen);
 	aw->a_salen = salen;
 
-	pthread_mutex_lock(&autowhite_change_lock);
-	TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
+	autowhite_insert_to_queue(aw);
 	TAILQ_INSERT_TAIL(&autowhite_buckets[BUCKET_HASH(aw->a_sa, 
 	    from, rcpt, AUTOWHITE_BUCKETS)].b_autowhite_head, aw, ab_list);
-	pthread_mutex_unlock(&autowhite_change_lock);
 
 	return aw;
 }
 
 void
-autowhite_put(aw)	/* autowhite list must be write-locked */
+autowhite_put(aw)	/* autowhite list must be locked */
 	struct autowhite *aw;
 {
-	pthread_mutex_lock(&autowhite_change_lock);
-	TAILQ_REMOVE(&autowhite_head, aw, a_list);	
+	TAILQ_REMOVE(&autowhite_head, aw, a_list);
 	TAILQ_REMOVE(&autowhite_buckets[BUCKET_HASH(aw->a_sa, 
 	    aw->a_from, aw->a_rcpt, AUTOWHITE_BUCKETS)].b_autowhite_head, 
 	    aw, ab_list);
-	pthread_mutex_unlock(&autowhite_change_lock);
 	free(aw->a_sa);
 	free(aw->a_from);
 	free(aw->a_rcpt);
@@ -482,7 +439,7 @@
 	struct autowhite *next_aw;
 	int count = 0;
 	
-	AUTOWHITE_WRLOCK;
+	AUTOWHITE_LOCK;
 	for (aw = TAILQ_FIRST(&autowhite_head); aw; aw = next_aw) {
 		next_aw = TAILQ_NEXT(aw, a_list);
 		
@@ -496,12 +453,10 @@
 
 			autowhite_put(aw);
 			count++;
-
-			dump_dirty++;
 		}
-		break;
 	}
 	AUTOWHITE_UNLOCK;
 	
+	dump_touch(count);
 	return count;
 }
diff -ru milter-greylist.maybeforged/autowhite.h milter-greylist.thread/autowhite.h
--- milter-greylist.maybeforged/autowhite.h	2006-08-20 05:53:26.000000000 +0000
+++ milter-greylist.thread/autowhite.h	2006-08-21 12:27:11.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: autowhite.h,v 1.20 2006/08/20 05:53:26 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -42,10 +43,6 @@
 #define AUTOWHITE_BUCKETS 512
 #endif
 
-#define AUTOWHITE_WRLOCK WRLOCK(autowhite_lock) 
-#define AUTOWHITE_RDLOCK RDLOCK(autowhite_lock) 
-#define AUTOWHITE_UNLOCK UNLOCK(autowhite_lock)
-
 TAILQ_HEAD(autowhitelist, autowhite);
 
 struct autowhite {
@@ -59,12 +56,12 @@
 };
 
 struct autowhite_bucket {
-	pthread_mutex_t	bucket_mtx;
 	TAILQ_HEAD(, autowhite) b_autowhite_head;
 };
 
-extern pthread_rwlock_t autowhite_lock;
-extern pthread_mutex_t autowhite_change_lock;
+#define AUTOWHITE_LOCK pthread_mutex_lock(&autowhite_lock)
+#define AUTOWHITE_UNLOCK pthread_mutex_unlock(&autowhite_lock)
+extern pthread_mutex_t autowhite_lock;
 
 void autowhite_init(void);
 struct autowhite *autowhite_get(struct sockaddr *, socklen_t, char *, char *,
diff -ru milter-greylist.maybeforged/conf.c milter-greylist.thread/conf.c
--- milter-greylist.maybeforged/conf.c	2006-07-27 08:20:12.000000000 +0000
+++ milter-greylist.thread/conf.c	2006-08-21 13:15:56.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: conf.c,v 1.38 2006/07/27 08:20:12 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -57,6 +58,9 @@
 #include <sys/stat.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/time.h>
 
 #include "acl.h"
 #ifdef USE_DNSRBL
@@ -69,65 +73,81 @@
 #include "dump.h"
 #include "milter-greylist.h"
 
-/* Default configuration */
-struct conf defconf;
-
-struct conf conf;
-
-char c_pidfile[QSTRLEN + 1];
-char c_dumpfile[QSTRLEN + 1];
-char c_socket[QSTRLEN + 1];
-char c_user[QSTRLEN + 1];
-char c_syncaddr[IPADDRSTRLEN + 1];
-char c_syncport[NUMLEN + 1];
-char c_syncsrcaddr[IPADDRSTRLEN + 1];
-char c_syncsrcport[NUMLEN + 1];
-char c_dracdb[QSTRLEN + 1];
+/* #define CONF_DEBUG */
 
+/* Default configuration */
+struct conf_rec defconf;
+pthread_key_t conf_key;
 char *conffile = CONFFILE;
-struct timeval conffile_modified;
-int numb_of_conf_update_threads;
 
-#define MAX_NUMB_OF_CONF_UPDATE_THREADS 1
-/*
- * this lock does not protect conf_update any more,
- * only conffile_modified and numb_of_conf_update_threads.
- * there are lot of non-auto variables above to protect as well,
- * so it is safer to limit the maximum number of configuration loading
- * processes to one for the time being.
- */
-pthread_rwlock_t conf_lock;
+#define CONF_LOCK pthread_mutex_lock(&conf_lock);
+#define CONF_UNLOCK pthread_mutex_unlock(&conf_lock);
+static pthread_mutex_t conf_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct conf_list conf_list_head;
+static pthread_cond_t conf_update_cond = PTHREAD_COND_INITIALIZER;
+static int conf_updating;
 
 void
 conf_init(void) {
 	int error;
 
-	if ((error = pthread_rwlock_init(&conf_lock, NULL)) != 0) {
+	TAILQ_INIT(&conf_list_head);
+
+	if ((error = pthread_key_create(&conf_key, 0)) != 0) {
 		syslog(LOG_ERR, 
-		    "pthread_rwlock_init failed: %s", strerror(error));
+		    "pthread_key_create failed: %s", strerror(error));
 		exit(EX_OSERR);
 	}
 
 	return;
 }
 
-void
-conf_load(void)
+#ifdef CONF_DEBUG
+static void
+conf_dump(void) {
+	struct conf_rec *c;
+
+	TAILQ_FOREACH_REVERSE(c, &conf_list_head, conf_list, c_chain) {
+		char textdate[DATELEN];
+		struct tm tm;
+		
+		localtime_r(&c->c_timestamp, &tm);
+		strftime(textdate, sizeof textdate, "%Y-%m-%d %T",  &tm);
+		syslog(LOG_DEBUG, "conf_dump: stamp %s ref %d",
+		       textdate, c->c_refcount);
+	}
+}
+#endif /* CONF_DEBUG */
+
+static void *
+conf_load_internal(timestamp)
+	void *timestamp;
 {
 	FILE *stream;
 	struct timeval tv1, tv2, tv3;
+	struct conf_rec *currconf, *threadconf, *newconf;
+
+	CONF_LOCK;
+	currconf = TAILQ_FIRST(&conf_list_head);
+	CONF_UNLOCK;
+	threadconf = GET_CONF();
 
+	if (!(newconf = (struct conf_rec *)malloc(sizeof *newconf))) {
+		syslog(LOG_ERR, "conf malloc failed: %s", strerror(errno));
+		exit(EX_OSERR);
+	}
 	/*
 	 * Reset the configuration to its default 
 	 * (This includes command line flags)
 	 */
-	memcpy(&conf, &defconf, sizeof(conf));
+	memcpy(newconf, &defconf, sizeof *newconf);
+	newconf->c_refcount = 1;
+	newconf->c_timestamp = *(time_t *)timestamp;
 
-	if (!conf.c_cold)
-		(void)gettimeofday(&tv1, NULL);
+	(void)gettimeofday(&tv1, NULL);
 
 	if ((stream = fopen(conffile, "r")) == NULL) {
-		if (conf.c_cold) {
+		if (!currconf) {
 			fprintf(stderr, "cannot open config file %s: %s\n", 
 			    conffile, strerror(errno));
 			fprintf(stderr, "continuing with no exception list\n");
@@ -136,6 +156,7 @@
 			    conffile, strerror(errno));
 		}
 	} else {
+		TSS_SET(conf_key, newconf);
 
 		peer_clear();
 		ACL_WRLOCK;
@@ -148,33 +169,51 @@
 		conf_line = 1;
 
 		conf_parse();
+		conf_dispose_input_file();
 		ACL_UNLOCK;
 
+		TSS_SET(conf_key, threadconf);
 		fclose(stream);
 
-		if (!conf.c_cold) {
+		if (!currconf || newconf->c_debug) {
 			(void)gettimeofday(&tv2, NULL);
 			timersub(&tv2, &tv1, &tv3);
 			syslog(LOG_DEBUG, "%sloaded config file in %ld.%06lds", 
-			conf.c_cold ? "" : "re",
+			(!currconf) ? "" : "re",
 			    tv3.tv_sec, tv3.tv_usec);
 		}
 	}
 
-	if (conf.c_cold) {
-		conf.c_cold = 0;
-		defconf.c_cold = 0;
-		(void)gettimeofday(&conffile_modified, NULL);
-	} else {
-		CONF_WRLOCK;
-		--numb_of_conf_update_threads;
-		CONF_UNLOCK;
+	/*
+	 * Dump the ACL for debugging purposes
+	 */
+	if (newconf->c_debug || newconf->c_acldebug)
+		acl_dump();
+
+	CONF_LOCK;
+	assert(TAILQ_FIRST(&conf_list_head) == currconf);
+	if (currconf && --currconf->c_refcount == 0) {
+		TAILQ_REMOVE(&conf_list_head, currconf, c_chain);
+		free(currconf);
 	}
+	TAILQ_INSERT_HEAD(&conf_list_head, newconf, c_chain);
+	CONF_UNLOCK;
 
-	if (conf.c_debug || conf.c_acldebug)
-		acl_dump();
+#ifdef CONF_DEBUG
+	conf_dump();
+#endif
+	dump_conf_changed();
+	return NULL;
+}
 
-	return;
+/* Functions other than main() must not invoke this */
+void
+conf_load(void) {
+	struct stat st;
+
+	if (stat(conffile, &st))
+		st.st_mtime = (time_t)0;
+	conf_load_internal(&st.st_mtime);
 }
 
 void
@@ -183,6 +222,7 @@
 	pthread_t tid;
 	pthread_attr_t attr;
 	int error;
+	int need_update;
 	
 	if (stat(conffile, &st) != 0) {
 		syslog(LOG_ERR, "config file \"%s\" unavailable", 
@@ -190,17 +230,16 @@
 		return;
 	}
 
-	CONF_WRLOCK;
-	numb_of_conf_update_threads++;
-	if (st.st_mtime <= conffile_modified.tv_sec ||
-		numb_of_conf_update_threads > MAX_NUMB_OF_CONF_UPDATE_THREADS) {
-		--numb_of_conf_update_threads;
-		CONF_UNLOCK;
-		return;
-	}
-	conffile_modified.tv_sec = st.st_mtime;
+	CONF_LOCK;
+	while (conf_updating)
+		pthread_cond_wait(&conf_update_cond, &conf_lock);
+	conf_updating = need_update =
+		st.st_mtime > TAILQ_FIRST(&conf_list_head)->c_timestamp;
 	CONF_UNLOCK;
 
+	if (!need_update)
+		return;
+
 	syslog(LOG_INFO, "reloading \"%s\"", conffile);
 
 	/*
@@ -230,27 +269,97 @@
 	}
 
 	if ((error = pthread_create(&tid, &attr, 
-	    (void *(*)(void *))conf_load, NULL)) != 0) {
+	    conf_load_internal, &st.st_mtime)) != 0) {
 		syslog(LOG_ERR, "pthread_create failed: %s", 
 		    strerror(error));
 		exit(EX_OSERR);
 	}
 
-	if ((error = pthread_detach(tid)) != 0) {
-		syslog(LOG_ERR, "pthread_detach failed: %s",
+	if ((error = pthread_attr_destroy(&attr)) != 0) {
+		syslog(LOG_ERR, "pthread_attr_destroy failed: %s",
 		    strerror(error));
 		exit(EX_OSERR);
 	}
 
-	if ((error = pthread_attr_destroy(&attr)) != 0) {
-		syslog(LOG_ERR, "pthread_attr_destroy failed: %s",
+	if ((error = pthread_join(tid, NULL)) != 0) {
+		syslog(LOG_ERR, "pthread_join failed: %s",
 		    strerror(error));
 		exit(EX_OSERR);
 	}
 
+	CONF_LOCK;
+	conf_updating = 0;
+	CONF_UNLOCK;
+	if ((error = pthread_cond_broadcast(&conf_update_cond)) != 0) {
+		syslog(LOG_ERR, "pthread_cond_broadcast failed: %s", 
+		       strerror(error));
+		abort();
+	}
+
 	return;
 }
 
+void
+conf_retain(void) {
+	struct conf_rec *c;
+
+	if (GET_CONF()) {
+		syslog(LOG_ERR, "%s:%d BUG: conf_retain called twice?\n",
+				__FILE__, __LINE__);
+		assert(0);
+	}
+
+	CONF_LOCK;
+	c = TAILQ_FIRST(&conf_list_head);
+#ifdef CONF_DEBUG
+	{
+		char textdate[DATELEN];
+		struct tm tm;
+		
+		localtime_r(&c->c_timestamp, &tm);
+		strftime(textdate, sizeof textdate, "%Y-%m-%d %T",  &tm);
+		syslog(LOG_DEBUG, "conf_retain: stamp %s ref %d -> %d",
+		       textdate, c->c_refcount, c->c_refcount + 1);
+	}
+#endif
+	++c->c_refcount;
+	CONF_UNLOCK;
+
+	TSS_SET(conf_key, c);
+}
+
+void
+conf_release(void) {
+	struct conf_rec *c = GET_CONF();
+
+	if (!c) {
+		syslog(LOG_ERR, "%s:%d BUG: conf_release before conf_retain\n",
+				__FILE__, __LINE__);
+		assert(0);
+		return;
+	}
+
+	CONF_LOCK;
+#ifdef CONF_DEBUG
+	{
+		char textdate[DATELEN];
+		struct tm tm;
+		
+		localtime_r(&c->c_timestamp, &tm);
+		strftime(textdate, sizeof textdate, "%Y-%m-%d %T",  &tm);
+		syslog(LOG_DEBUG, "conf_release: stamp %s ref %d -> %d",
+		       textdate, c->c_refcount, c->c_refcount - 1);
+	}
+#endif
+	if (--c->c_refcount == 0) {
+		TAILQ_REMOVE(&conf_list_head, c, c_chain);
+		free(c);
+	}
+	CONF_UNLOCK;
+
+	TSS_SET(conf_key, NULL);
+}
+
 /*
  * Write path into dst, stripping leading and trailing quotes
  */
@@ -273,9 +382,11 @@
 
 void
 conf_defaults(c)
-	struct conf *c;
+	struct conf_rec *c;
 {
-	c->c_cold = 1;
+	c->c_refcount = -1;
+	c->c_timestamp = (time_t)0;
+
 	c->c_forced = C_GLNONE;
 	c->c_debug = 0;
 	c->c_acldebug = 0;
diff -ru milter-greylist.maybeforged/conf.h milter-greylist.thread/conf.h
--- milter-greylist.maybeforged/conf.h	2006-07-26 13:03:11.000000000 +0000
+++ milter-greylist.thread/conf.h	2006-08-21 02:44:15.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: conf.h,v 1.36 2006/07/26 13:03:11 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -42,7 +43,6 @@
 #include <stdio.h>
 #include <pthread.h>
 #include <sys/socket.h>
-#include <sys/time.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
@@ -56,12 +56,12 @@
 #define DRACDB "/usr/local/etc/dracdb.db"
 #endif
 
-#define CONF_WRLOCK WRLOCK(conf_lock) 
-#define CONF_RDLOCK RDLOCK(conf_lock) 
-#define CONF_UNLOCK UNLOCK(conf_lock)
+TAILQ_HEAD(conf_list, conf_rec);
+struct conf_rec {
+	int c_refcount;
+	time_t c_timestamp;
+	TAILQ_ENTRY(conf_rec) c_chain;
 
-struct conf {
-	int c_cold;
 	int c_forced;
 	int c_debug;
 	int c_acldebug;
@@ -95,6 +95,16 @@
 	int c_nodrac;
 	int c_dump_no_time_translation;
       int c_logexpired;
+
+	char c_pidfile_storage[QSTRLEN + 1];
+	char c_dumpfile_storage[QSTRLEN + 1];
+	char c_socket_storage[QSTRLEN + 1];
+	char c_user_storage[QSTRLEN + 1];
+	char c_syncaddr_storage[IPADDRSTRLEN + 1];
+	char c_syncport_storage[NUMLEN + 1];
+	char c_syncsrcaddr_storage[IPADDRSTRLEN + 1];
+	char c_syncsrcport_storage[NUMLEN + 1];
+	char c_dracdb_storage[QSTRLEN + 1];
 };
 
 /* c_forced flags */
@@ -123,29 +133,25 @@
 #define C_NODELAYS	0x2
 #define C_ALL		0x3
 
-extern struct conf defconf;
-extern struct conf conf;
+extern struct conf_rec defconf;
+extern pthread_key_t conf_key;
+#define GET_CONF() ((struct conf_rec *)pthread_getspecific(conf_key))
+#define conf (*GET_CONF())
 extern char *conffile;
-extern char c_pidfile[QSTRLEN + 1];
-extern char c_dumpfile[QSTRLEN + 1];
-extern char c_socket[QSTRLEN + 1];
-extern char c_user[QSTRLEN + 1];
-extern char c_syncaddr[IPADDRSTRLEN + 1];
-extern char c_syncport[NUMLEN + 1];
-extern char c_syncsrcaddr[IPADDRSTRLEN + 1];
-extern char c_syncsrcport[NUMLEN + 1];
-extern char c_dracdb[QSTRLEN + 1];
-
-extern pthread_rwlock_t conf_lock;
 
 void conf_init(void);
 void conf_load(void);
 void conf_update(void);
+void conf_retain(void);
+void conf_release(void);
 
 extern FILE *conf_in;
 extern int conf_line;
+
 int conf_parse(void);
+void conf_dispose_input_file(void);
 char *quotepath(char *, char *, size_t);
-void conf_defaults(struct conf *);
+char *quotepath_alloc(const char *);
+void conf_defaults(struct conf_rec *);
 
 #endif /* _CONF_H_ */
diff -ru milter-greylist.maybeforged/conf_lex.l milter-greylist.thread/conf_lex.l
--- milter-greylist.maybeforged/conf_lex.l	2006-08-21 05:12:41.000000000 +0000
+++ milter-greylist.thread/conf_lex.l	2006-08-21 13:16:15.000000000 +0000
@@ -89,6 +89,7 @@
 
 	#include <string.h>
 	#include <errno.h>
+	#include <syslog.h>
 	#include <sysexits.h>
 
 	#define YY_NO_UNPUT
@@ -221,6 +222,11 @@
 yyerror(err)
 	char *err;
 {
-	fprintf(stderr, "config error at line %d: %s\n", conf_line, err);
+	syslog(LOG_ERR, "config error at line %d: %s", conf_line, err);
 	return;
 }
+
+void
+conf_dispose_input_file(void) {
+	YY_FLUSH_BUFFER;
+}
diff -ru milter-greylist.maybeforged/conf_yacc.y milter-greylist.thread/conf_yacc.y
--- milter-greylist.maybeforged/conf_yacc.y	2006-08-21 05:13:37.000000000 +0000
+++ milter-greylist.thread/conf_yacc.y	2006-08-20 22:11:32.000000000 +0000
@@ -245,12 +245,12 @@
 	;
 pidfile:	PIDFILE QSTRING	{ if (C_NOTFORCED(C_PIDFILE)) 
 					conf.c_pidfile = 
-					    quotepath(c_pidfile, $2, QSTRLEN);
+					    quotepath(conf.c_pidfile_storage, $2, QSTRLEN);
 				}
 	;
 dumpfile:	GLDUMPFILE QSTRING{ if (C_NOTFORCED(C_DUMPFILE)) 
 					conf.c_dumpfile = 
-					    quotepath(c_dumpfile, $2, QSTRLEN);
+					    quotepath(conf.c_dumpfile_storage, $2, QSTRLEN);
 				}
 	;
 subnetmatch:	SUBNETMATCH CIDR{ if (C_NOTFORCED(C_MATCHMASK))
@@ -269,12 +269,12 @@
 	;
 socket:		SOCKET QSTRING	{ if (C_NOTFORCED(C_SOCKET)) 
 					conf.c_socket = 
-					    quotepath(c_socket, $2, QSTRLEN);
+					    quotepath(conf.c_socket_storage, $2, QSTRLEN);
 				}
 	;
 user:		USER QSTRING	{ if (C_NOTFORCED(C_USER))
 					conf.c_user =
-					    quotepath(c_user, $2, QSTRLEN);
+					    quotepath(conf.c_user_storage, $2, QSTRLEN);
 				}
 	;	
 report:		REPORT NONE	{ conf.c_report = C_GLNONE; }
@@ -301,22 +301,22 @@
 				   conf.c_syncport = NULL;
 				}
 	|	SYNCADDR IPADDR	{
-				if (IP4TOSTRING($2, c_syncaddr) == NULL) {
+				if (IP4TOSTRING($2, conf.c_syncaddr_storage) == NULL) {
 					printf("invalid IPv4 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncaddr = c_syncaddr;
+				conf.c_syncaddr = conf.c_syncaddr_storage;
 				conf.c_syncport = NULL;
 	                        }
 	|	SYNCADDR IP6ADDR {
 #ifdef AF_INET6
-				if (IP6TOSTRING($2, c_syncaddr) == NULL) {
+				if (IP6TOSTRING($2, conf.c_syncaddr_storage) == NULL) {
 					printf("invalid IPv6 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncaddr = c_syncaddr;
+				conf.c_syncaddr = conf.c_syncaddr_storage;
 				conf.c_syncport = NULL;
 #else /* AF_INET6 */
 				printf("IPv6 is not supported, "
@@ -325,30 +325,30 @@
 				}
 	|	SYNCADDR STAR PORT TNUMBER {
 				conf.c_syncaddr = NULL;
-				conf.c_syncport = c_syncport;
+				conf.c_syncport = conf.c_syncport_storage;
 				strncpy(conf.c_syncport, $4, NUMLEN);
 				conf.c_syncport[NUMLEN] = '\0';
 				}
 	|	SYNCADDR IPADDR PORT TNUMBER {
-				if (IP4TOSTRING($2, c_syncaddr) == NULL) {
+				if (IP4TOSTRING($2, conf.c_syncaddr_storage) == NULL) {
 					printf("invalid IPv4 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncaddr = c_syncaddr;
-				conf.c_syncport = c_syncport;
+				conf.c_syncaddr = conf.c_syncaddr_storage;
+				conf.c_syncport = conf.c_syncport_storage;
 				strncpy(conf.c_syncport, $4, NUMLEN);
 				conf.c_syncport[NUMLEN] = '\0';
 				}
 	|	SYNCADDR IP6ADDR PORT TNUMBER {
 #ifdef AF_INET6
-				if (IP6TOSTRING($2, c_syncaddr) == NULL) {
+				if (IP6TOSTRING($2, conf.c_syncaddr_storage) == NULL) {
 					printf("invalid IPv6 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncaddr = c_syncaddr;
-				conf.c_syncport = c_syncport;
+				conf.c_syncaddr = conf.c_syncaddr_storage;
+				conf.c_syncport = conf.c_syncport_storage;
 				strncpy(conf.c_syncport, $4, NUMLEN);
 				conf.c_syncport[NUMLEN] = '\0';
 #else /* AF_INET6 */
@@ -363,22 +363,22 @@
 				   conf.c_syncsrcport = NULL;
 				}
 	|	SYNCSRCADDR IPADDR	{
-				if (IP4TOSTRING($2, c_syncsrcaddr) == NULL) {
+				if (IP4TOSTRING($2, conf.c_syncsrcaddr_storage) == NULL) {
 					printf("invalid IPv4 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncsrcaddr = c_syncsrcaddr;
+				conf.c_syncsrcaddr = conf.c_syncsrcaddr_storage;
 				conf.c_syncsrcport = NULL;
 	                        }
 	|	SYNCSRCADDR IP6ADDR {
 #ifdef AF_INET6
-				if (IP6TOSTRING($2, c_syncsrcaddr) == NULL) {
+				if (IP6TOSTRING($2, conf.c_syncsrcaddr_storage) == NULL) {
 					printf("invalid IPv6 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncsrcaddr = c_syncsrcaddr;
+				conf.c_syncsrcaddr = conf.c_syncsrcaddr_storage;
 				conf.c_syncsrcport = NULL;
 #else /* AF_INET6 */
 				printf("IPv6 is not supported, "
@@ -387,30 +387,30 @@
 				}
 	|	SYNCSRCADDR STAR PORT TNUMBER {
 				conf.c_syncsrcaddr = NULL;
-				conf.c_syncsrcport = c_syncsrcport;
+				conf.c_syncsrcport = conf.c_syncsrcport_storage;
 				strncpy(conf.c_syncsrcport, $4, NUMLEN);
 				conf.c_syncsrcport[NUMLEN] = '\0';
 				}
 	|	SYNCSRCADDR IPADDR PORT TNUMBER {
-				if (IP4TOSTRING($2, c_syncsrcaddr) == NULL) {
+				if (IP4TOSTRING($2, conf.c_syncsrcaddr_storage) == NULL) {
 					printf("invalid IPv4 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncsrcaddr = c_syncsrcaddr;
-				conf.c_syncsrcport = c_syncsrcport;
+				conf.c_syncsrcaddr = conf.c_syncsrcaddr_storage;
+				conf.c_syncsrcport = conf.c_syncsrcport_storage;
 				strncpy(conf.c_syncsrcport, $4, NUMLEN);
 				conf.c_syncsrcport[NUMLEN] = '\0';
 				}
 	|	SYNCSRCADDR IP6ADDR PORT TNUMBER {
 #ifdef AF_INET6
-				if (IP6TOSTRING($2, c_syncsrcaddr) == NULL) {
+				if (IP6TOSTRING($2, conf.c_syncsrcaddr_storage) == NULL) {
 					printf("invalid IPv6 address "
 					    "line %d\n", conf_line);
 					exit(EX_DATAERR);
 				}
-				conf.c_syncsrcaddr = c_syncsrcaddr;
-				conf.c_syncsrcport = c_syncsrcport;
+				conf.c_syncsrcaddr = conf.c_syncsrcaddr_storage;
+				conf.c_syncsrcport = conf.c_syncsrcport_storage;
 				strncpy(conf.c_syncsrcport, $4, NUMLEN);
 				conf.c_syncsrcport[NUMLEN] = '\0';
 #else /* AF_INET6 */
@@ -562,7 +562,7 @@
 dracdb:			DRAC DB QSTRING	{ 
 #ifdef USE_DRAC
 				conf.c_dracdb = 
-					    quotepath(c_dracdb, $3, QSTRLEN);
+					    quotepath(conf.c_dracdb_storage, $3, QSTRLEN);
 #else
 				printf("DRAC support not compiled "
 				    "in line %d\n", conf_line);
diff -ru milter-greylist.maybeforged/dump.c milter-greylist.thread/dump.c
--- milter-greylist.maybeforged/dump.c	2006-01-08 00:38:25.000000000 +0000
+++ milter-greylist.thread/dump.c	2006-08-21 05:36:33.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: dump.c,v 1.25 2006/01/08 00:38:25 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -69,10 +70,27 @@
 #include "autowhite.h"
 #include "milter-greylist.h"
 
-pthread_cond_t dump_sleepflag;
+/*
+ * The dump_dirty indicates number of changes from the last dump, but
+ * inaccurately. This is something like a condition variable. If someone
+ * increments dump_dirty, it is ensured that the dumper updates the dump
+ * file in future, but not always immediately. Dumping might also occur
+ * even though unnecessary.
+ */
+
+static pthread_mutex_t dump_todo_lock = PTHREAD_MUTEX_INITIALIZER;
+static int dump_todo = 0;
+static pthread_cond_t dump_sleepflag;
+#define DUMP_TODO_CONF_UPDATE 0x1
+#define DUMP_TODO_FLUSH 0x2
+#define DUMP_TODO_TERMINATE 0x4
 
 int dump_parse(void);
-int dump_dirty = 0;
+void dump_dispose_input_file(void);
+static pthread_mutex_t dump_dirty_lock = PTHREAD_MUTEX_INITIALIZER;
+static int dump_dirty = 0;
+
+static pthread_t dumper_tid;
 
 void
 dump_init(void) {
@@ -89,10 +107,9 @@
 
 void
 dumper_start(void) {
-	pthread_t tid;
 	int error;
 
-	if ((error = pthread_create(&tid, NULL, dumper, NULL)) != 0) {
+	if ((error = pthread_create(&dumper_tid, NULL, dumper, NULL)) != 0) {
 		syslog(LOG_ERR,
 		    "cannot start dumper thread: %s", strerror(error));
 		exit(EX_OSERR);
@@ -105,58 +122,87 @@
 dumper(dontcare) 
 	void *dontcare;
 {
-	int error;
-	pthread_mutex_t mutex;
-
-	if ((error = pthread_mutex_init(&mutex, NULL)) != 0) {
-		syslog(LOG_ERR, "pthread_mutex_init failed: %s\n",
-		    strerror(error));
-		exit(EX_OSERR);
-	}
-
-	if ((error = pthread_mutex_lock(&mutex)) != 0) {
-		syslog(LOG_ERR, "pthread_mutex_lock failed: %s\n", 
-		    strerror(error));
-		exit(EX_OSERR);
-	}
+	struct conf_rec *confp;
+	struct timeval start;
 
+	conf_retain();
+	confp = GET_CONF();
+	gettimeofday(&start, NULL);
 	for (;;) {
-		/* XXX Not really dynamically adjustable */
-		switch (conf.c_dumpfreq) {
-		case -1:
-			sleep(DUMPFREQ);
-			break;
-
-		case 0:
-			if ((error = pthread_cond_wait(&dump_sleepflag, 
-			    &mutex)) != 0)
-			    syslog(LOG_ERR, "pthread_cond_wait failed: %s\n",
-				strerror(error));
-			break;
-
-		default:
-			sleep(conf.c_dumpfreq);
-			break;
+		int error;
+		int todo;
+		
+		pthread_mutex_lock(&dump_todo_lock);
+		while (dump_todo == 0 ||
+		       (confp->c_dumpfreq != 0 &&
+			dump_todo == DUMP_TODO_FLUSH)) {
+			if (confp->c_dumpfreq > 0) {
+				struct timeval now;
+				struct timespec timeout;
+
+				gettimeofday(&now, NULL);
+				timeout.tv_sec = now.tv_sec +
+					confp->c_dumpfreq - start.tv_sec;
+				timeout.tv_nsec =
+					(now.tv_usec - start.tv_usec) * 1000;
+				if (timeout.tv_nsec < 0) {
+					--timeout.tv_sec;
+					timeout.tv_nsec += 1000000000;
+				}
+				if (timeout.tv_sec < 0)
+					break;
+				error = pthread_cond_timedwait(&dump_sleepflag,
+							       &dump_todo_lock,
+							       &timeout);
+			} else {
+				error = pthread_cond_wait(&dump_sleepflag,
+							  &dump_todo_lock);
+			}
+			if (error != 0 && error != ETIMEDOUT) {
+				syslog(LOG_ERR,
+				       "pthread_cond_(timed)wait failed: %s",
+				       strerror(error));
+				abort();
+			}
 		}
+		todo = (dump_todo & DUMP_TODO_CONF_UPDATE) ?
+			DUMP_TODO_CONF_UPDATE :
+			(dump_todo & DUMP_TODO_TERMINATE) ?
+			(DUMP_TODO_FLUSH | DUMP_TODO_TERMINATE):
+			DUMP_TODO_FLUSH;
+		dump_todo &= ~todo;
+		pthread_mutex_unlock(&dump_todo_lock);
 
 		/*
-		 * If there is no change to dump, go back to sleep
+		 * Since following operations require locking, so we do
+		 * them outside the above loop.
 		 */
-		if ((conf.c_dumpfreq == -1) || (dump_dirty == 0))
-			continue;
-
-		dump_perform();
+		switch (todo) {
+			case DUMP_TODO_CONF_UPDATE:
+				conf_release();
+				conf_retain();
+				confp = GET_CONF();
+				break;
+			case DUMP_TODO_FLUSH | DUMP_TODO_TERMINATE:
+				dump_perform(1);
+				break;
+			case DUMP_TODO_FLUSH:
+				dump_perform(0);
+				gettimeofday(&start, NULL);
+				break;
+		}
+		if (todo & DUMP_TODO_TERMINATE)
+			break;
 	}
-
-	/* NOTREACHED */
-	syslog(LOG_ERR, "dumper unexpectedly exitted");
-	exit(EX_SOFTWARE);
+	conf_release();
 
 	return NULL;
 }
 
 void
-dump_perform(void) {
+dump_perform(final)
+	int final;
+{
 	FILE *dump;
 	int dumpfd;
 	struct timeval tv1, tv2, tv3;
@@ -165,22 +211,31 @@
 	int greylisted_count;
 	int whitelisted_count;
 	char *s_buffer = NULL;
+	int dirty;
+
+	pthread_mutex_lock(&dump_dirty_lock);
+	dirty = dump_dirty;
+	dump_dirty = 0;
+	pthread_mutex_unlock(&dump_dirty_lock);
+
+	if (final)
+		syslog(LOG_INFO,
+		       dirty ? "Final database dump" :
+			       "Final database dump: no change to dump");
+
+	/*
+	 * If there is no change to dump, go back to sleep
+	 */
+	if (!dirty)
+		return;
 
 	if (conf.c_debug) {
 		(void)gettimeofday(&tv1, NULL);
 		syslog(LOG_DEBUG, "dumping %d modifications", 
-		    dump_dirty);
+		    dirty);
 	}
 
 	/* 
-	 * dump_dirty is not protected by a lock,
-	 * hence it could be modified between the 
-	 * display and the actual dump. This debug
-	 * message does not give an accurate information
-	 */
-	dump_dirty = 0;
-
-	/* 
 	 * Dump the database in a temporary file and 
 	 * then replace the old one by the new one.
 	 * On decent systems, rename(2) garantees that 
@@ -253,19 +308,23 @@
 		syslog(LOG_ERR, "starting with an empty greylist");
 	} else {
 		dump_in = dump;
-		PENDING_WRLOCK;
-		AUTOWHITE_WRLOCK;
+		PENDING_LOCK;
+		AUTOWHITE_LOCK;
+
 		dump_parse();
+		dump_dispose_input_file();
+
+		AUTOWHITE_UNLOCK;
+		PENDING_UNLOCK;
+		fclose(dump);
 
 		/* 
 		 * dump_dirty has been bumped on each pending_get call,
 		 * whereas there is nothing to flush. Fix that.
 		 */
+		pthread_mutex_lock(&dump_dirty_lock);
 		dump_dirty = 0;
-
-		AUTOWHITE_UNLOCK;
-		PENDING_UNLOCK;
-		fclose(dump);
+		pthread_mutex_unlock(&dump_dirty_lock);
 	}
 
 	return;
@@ -275,6 +334,9 @@
 dump_flush(void) {
 	int error;
 
+	pthread_mutex_lock(&dump_todo_lock);
+	dump_todo |= DUMP_TODO_FLUSH;
+	pthread_mutex_unlock(&dump_todo_lock);
 	if ((error = pthread_cond_signal(&dump_sleepflag)) != 0) {
 		syslog(LOG_ERR, "cannot wakeup dumper: %s", strerror(error));
 		exit(EX_SOFTWARE);
@@ -303,3 +365,44 @@
 
 	return;
 }
+
+void
+dump_touch(n_modifications)
+	int n_modifications;
+{
+	if (n_modifications) {
+		pthread_mutex_lock(&dump_dirty_lock);
+		dump_dirty += n_modifications;
+		pthread_mutex_unlock(&dump_dirty_lock);
+	}
+
+	return;
+}
+
+void
+dump_conf_changed(void) {
+	pthread_mutex_lock(&dump_todo_lock);
+	dump_todo |= DUMP_TODO_CONF_UPDATE;
+	pthread_mutex_unlock(&dump_todo_lock);
+	pthread_cond_signal(&dump_sleepflag);
+
+	return;
+}
+
+void
+dumper_stop(void) {
+	int error;
+
+	pthread_mutex_lock(&dump_todo_lock);
+	dump_todo |= DUMP_TODO_TERMINATE;
+	pthread_mutex_unlock(&dump_todo_lock);
+	pthread_cond_signal(&dump_sleepflag);
+
+	if ((error = pthread_join(dumper_tid, NULL)) != 0) {
+		syslog(LOG_ERR, "pthread_join failed: %s",
+		    strerror(error));
+		exit(EX_OSERR);
+	}
+
+	return;
+}
diff -ru milter-greylist.maybeforged/dump.h milter-greylist.thread/dump.h
--- milter-greylist.maybeforged/dump.h	2004-09-13 18:41:55.000000000 +0000
+++ milter-greylist.thread/dump.h	2006-08-18 04:14:36.000000000 +0000
@@ -67,10 +67,8 @@
 #include "milter-greylist.h"
 
 extern FILE *dump_in;
-extern int dump_dirty;
 extern int dump_line;
 extern char *dumpfile;
-extern int dump_dirty;
 
 void dump_init(void);
 void *dumper(void *);
@@ -78,6 +76,9 @@
 void dump_flush(void);
 void dump_reload(void);
 void dump_header(FILE *);
-void dump_perform(void);
+void dump_perform(int);
+void dump_touch(int);
+void dump_conf_changed(void);
+void dumper_stop(void);
 
 #endif /* _DUMP_H_ */
diff -ru milter-greylist.maybeforged/dump_lex.l milter-greylist.thread/dump_lex.l
--- milter-greylist.maybeforged/dump_lex.l	2005-01-08 14:43:30.000000000 +0000
+++ milter-greylist.thread/dump_lex.l	2006-08-21 02:19:44.000000000 +0000
@@ -88,3 +88,8 @@
 	syslog(LOG_DEBUG, "dump error at line %d: %s\n", dump_line, err);
 	return;
 }
+
+void
+dump_dispose_input_file(void) {
+	YY_FLUSH_BUFFER;
+}
diff -ru milter-greylist.maybeforged/milter-greylist.c milter-greylist.thread/milter-greylist.c
--- milter-greylist.maybeforged/milter-greylist.c	2006-08-21 12:12:08.000000000 +0000
+++ milter-greylist.thread/milter-greylist.c	2006-08-21 13:11:53.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: milter-greylist.c,v 1.129 2006/08/20 06:38:43 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -91,6 +92,13 @@
 static void log_and_report_greylisting(SMFICTX *, struct mlfi_priv *, char *);
 static void reset_acl_values(struct mlfi_priv *);
 
+static sfsistat real_connect(SMFICTX *, char *, _SOCK_ADDR *);
+static sfsistat real_helo(SMFICTX *, char *);
+static sfsistat real_envfrom(SMFICTX *, char **);
+static sfsistat real_envrcpt(SMFICTX *, char **);
+static sfsistat real_eom(SMFICTX *);
+static sfsistat real_close(SMFICTX *);
+
 struct smfiDesc smfilter =
 {
 	"greylist",	/* filter name */
@@ -114,6 +122,87 @@
 	char *hostname;
 	_SOCK_ADDR *addr;
 {
+	sfsistat r;
+
+	conf_retain();
+	r = real_connect(ctx, hostname, addr);
+	conf_release();
+	return r;
+}
+
+sfsistat
+mlfi_helo(ctx, helostr)
+	SMFICTX *ctx;
+	char *helostr;
+{
+	sfsistat r;
+
+	conf_retain();
+	r = real_helo(ctx, helostr);
+	conf_release();
+	return r;
+}
+
+sfsistat
+mlfi_envfrom(ctx, envfrom)
+	SMFICTX *ctx;
+	char **envfrom;
+{
+	sfsistat r;
+
+	/*
+	 * Reload the config file if it has been touched
+	 */
+	conf_update();
+	conf_retain();
+	r = real_envfrom(ctx, envfrom);
+	conf_release();
+	return r;
+}
+
+sfsistat
+mlfi_envrcpt(ctx, envrcpt)
+	SMFICTX *ctx;
+	char **envrcpt;
+{
+	sfsistat r;
+
+	conf_retain();
+	r = real_envrcpt(ctx, envrcpt);
+	conf_release();
+	return r;
+}
+
+sfsistat
+mlfi_eom(ctx)
+	SMFICTX *ctx;
+{
+	sfsistat r;
+
+	conf_retain();
+	r = real_eom(ctx);
+	conf_release();
+	return r;
+}
+
+sfsistat
+mlfi_close(ctx)
+	SMFICTX *ctx;
+{
+	sfsistat r;
+
+	conf_retain();
+	r = real_close(ctx);
+	conf_release();
+	return r;
+}
+
+static sfsistat
+real_connect(ctx, hostname, addr)
+	SMFICTX *ctx;
+	char *hostname;
+	_SOCK_ADDR *addr;
+{
 	struct mlfi_priv *priv;
 	const char *client_resolve;
 
@@ -167,8 +256,8 @@
 	return SMFIS_CONTINUE;
 }
 
-sfsistat
-mlfi_helo(ctx, helostr)
+static sfsistat
+real_helo(ctx, helostr)
 	SMFICTX *ctx;
 	char *helostr;
 {
@@ -186,8 +275,8 @@
 }
 
 
-sfsistat
-mlfi_envfrom(ctx, envfrom)
+static sfsistat
+real_envfrom(ctx, envfrom)
 	SMFICTX *ctx;
 	char **envfrom;
 {
@@ -224,11 +313,6 @@
 	priv->priv_from[ADDRLEN] = '\0';
 
 	/*
-	 * Reload the config file if it has been touched
-	 */
-	conf_update();
-
-	/*
 	 * Is the sender non-IP?
 	 */
 	if (priv->priv_whitelist & EXF_NONIP)
@@ -289,8 +373,8 @@
 	return SMFIS_CONTINUE;
 }
 
-sfsistat
-mlfi_envrcpt(ctx, envrcpt)
+static sfsistat
+real_envrcpt(ctx, envrcpt)
 	SMFICTX *ctx;
 	char **envrcpt;
 {
@@ -470,8 +554,8 @@
 	return SMFIS_TEMPFAIL;
 }
 
-sfsistat
-mlfi_eom(ctx)
+static sfsistat
+real_eom(ctx)
 	SMFICTX *ctx;
 {
 	struct mlfi_priv *priv;
@@ -626,8 +710,8 @@
 	return SMFIS_CONTINUE;
 }
 
-sfsistat
-mlfi_close(ctx)
+static sfsistat
+real_close(ctx)
 	SMFICTX *ctx;
 {
 	struct mlfi_priv *priv;
@@ -646,8 +730,7 @@
 	/*
 	 * If we need to dump on each change and something changed, dump
 	 */
-	if ((dump_dirty != 0) && (conf.c_dumpfreq == 0))
-		dump_flush();
+	dump_flush();
 
 	return SMFIS_CONTINUE;
 }
@@ -661,6 +744,10 @@
 {
 	int ch;
 	int checkonly = 0;
+	int exitval;
+
+	openlog("milter-greylist", LOG_PERROR, LOG_MAIL);
+
 	/*
 	 * Load configuration defaults
 	 */
@@ -860,13 +947,6 @@
 		}
 	}
 	
-	if (checkonly) {
-		conf_init();
-		all_list_init();
-		acl_init ();
-		conf_load();
-		exit(0);
-	}
 	/* 
 	 * Register our callbacks 
 	 */
@@ -896,11 +976,14 @@
 	 * can access the list yet.
 	 */
 	conf_load();
+	if (checkonly)
+		return 0;
+	conf_retain();
 
-	if (conf.c_nodetach != 0)
-		openlog("milter-greylist", LOG_PERROR, LOG_MAIL);
-	else
+	if (!conf.c_nodetach) {
+		closelog();
 		openlog("milter-greylist", 0, LOG_MAIL);
+	}
 
 	
 	if (conf.c_socket == NULL) {
@@ -1011,24 +1094,14 @@
 	sync_sender_start();
 
 	/*
-	 * Install an atexit() callback to perform
-	 * a dump when milter-greylist exits.
-	 */
-	if (atexit(*final_dump) != 0) {
-		syslog(LOG_ERR, "atexit() failed: %s", strerror(errno));
-		exit(EX_OSERR);
-	}	
-
-	/*
-	 * Dump the ACL for debugging purposes
-	 */
-	if (conf.c_debug || conf.c_acldebug)
-		acl_dump();
-
-	/*
 	 * Here we go!
 	 */
-	return smfi_main();
+	conf_release();
+	exitval = smfi_main();
+	fprintf(stderr, "smfi_main() returned %d\n", exitval);
+	
+	dumper_stop();
+	return exitval;
 }
 
 void
@@ -1317,20 +1390,6 @@
 	return;
 }
 
-void
-final_dump(void) {
-
-	if (dump_dirty != 0) {
-		syslog(LOG_INFO, "Final database dump");
-		dump_perform();
-	} else {
-		syslog(LOG_INFO, "Final database dump: no change to dump");
-	}
-
-	syslog(LOG_INFO, "Exiting");
-	return;
-}
-
 #ifdef	USE_DRAC
 static int
 check_drac(dotted_ip)
diff -ru milter-greylist.maybeforged/milter-greylist.h milter-greylist.thread/milter-greylist.h
--- milter-greylist.maybeforged/milter-greylist.h	2006-08-21 05:15:46.000000000 +0000
+++ milter-greylist.thread/milter-greylist.h	2006-08-21 05:36:43.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: milter-greylist.h,v 1.44 2006/08/20 06:38:43 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -229,6 +230,16 @@
 }
 #endif
 
+#define TSS_SET(key, val) do {						  \
+	int err;							  \
+									  \
+	if ((err = pthread_setspecific(key, val)) != 0) {		  \
+		syslog(LOG_ERR, "%s:%d pthread_setspecific failed: %s",	  \
+		    __FILE__, __LINE__, strerror(err));			  \
+		exit(EX_SOFTWARE);					  \
+	}								  \
+} while (/*CONSTCOND*/ 0)
+
 /*
  * Some systems don't know about LOG_PERROR. By defining it
  * to zero, we make it nilpotent
diff -ru milter-greylist.maybeforged/milter-greylist.spec milter-greylist.thread/milter-greylist.spec
--- milter-greylist.maybeforged/milter-greylist.spec	2006-08-20 06:38:43.000000000 +0000
+++ milter-greylist.thread/milter-greylist.spec	2006-08-21 12:44:59.000000000 +0000
@@ -1,8 +1,8 @@
-# $Id: milter-greylist.spec,v 1.49 2006/08/20 06:38:43 manu Exp $
+# $Id: milter-greylist.spec.in,v 1.4 2005/05/11 19:57:45 manu Exp $
 # Contributed by Ivan F. Martinez
 %define ver 3.0a2
 %define rel 1
-%define user root
+%define user smmsp
 
 Summary: GreyList milter for sendmail
 Name: milter-greylist
diff -ru milter-greylist.maybeforged/pending.c milter-greylist.thread/pending.c
--- milter-greylist.maybeforged/pending.c	2006-08-20 05:53:26.000000000 +0000
+++ milter-greylist.thread/pending.c	2006-08-21 05:33:31.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: pending.c,v 1.78 2006/08/20 05:53:26 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -70,21 +71,23 @@
 #include "autowhite.h"
 #include "milter-greylist.h"
 
-struct pendinglist pending_head;
+static void pending_insert_to_queue(struct pending *);
+
 struct pending_bucket *pending_buckets;
-pthread_rwlock_t pending_lock; 	/* protects pending_head and dump_dirty */
-pthread_mutex_t pending_change_lock;
+struct pendinglist pending_head;
+/* protects pending_head and pending_buckets */
+pthread_mutex_t pending_lock = PTHREAD_MUTEX_INITIALIZER;
+
 /*
  * protects pending->p_refcnt
  * since we hold many pending entries, requied memory for the lock
  * object is considerably large to have it in each pending entry.  so,
  * we use just one lock object for all pending entries.
  */
-static pthread_rwlock_t refcnt_lock;
+static pthread_mutex_t refcnt_lock = PTHREAD_MUTEX_INITIALIZER;
 
 void
 pending_init(void) {
-	int error;
 	int i;
 
 	TAILQ_INIT(&pending_head);
@@ -96,30 +99,43 @@
 		exit(EX_OSERR);
 	}
 	
-	if ((error = pthread_rwlock_init(&pending_lock, NULL)) != 0 ||
-	    (error = pthread_rwlock_init(&refcnt_lock, NULL)) != 0 ||
-	    (error = pthread_mutex_init(&pending_change_lock, NULL)) != 0) {
-		syslog(LOG_ERR, 
-		    "pthread_rwlock_init failed: %s", strerror(error));
-		exit(EX_OSERR);
-	}
-	
 	for(i = 0; i < PENDING_BUCKETS; i++) {
 		TAILQ_INIT(&pending_buckets[i].b_pending_head);
-		
-		if ((error = pthread_mutex_init(&pending_buckets[i].bucket_mtx,
-		    NULL)) != 0) {
-			syslog(LOG_ERR, 
-			    "pthread_mutex_init failed: %s", strerror(error));
-			exit(EX_OSERR);
-		}
-		
 	}
 
 	return;
 }
 
-/* pending_lock must be write-locked & bucket must be locked */
+/*
+ * A new entry is inserted to the back of the queue in most cases,
+ * but it is not true in these situations:
+ * - The conf.c_delay was shortened
+ * - System clock was turned to the past
+ *
+ * To ensure that the queue is sorted by expiration times (p_tv),
+ * we need to find the right position where to insert a new entry.
+ */
+
+/* pending_lock must be locked */
+static void
+pending_insert_to_queue(newentry)
+	struct pending *newentry;
+{
+	struct pending *p;
+	
+	TAILQ_FOREACH_REVERSE(p, &pending_head, pendinglist, p_list) {
+		if (p->p_tv.tv_sec < newentry->p_tv.tv_sec ||
+		    (p->p_tv.tv_sec == newentry->p_tv.tv_sec &&
+		     p->p_tv.tv_usec < newentry->p_tv.tv_usec))
+			break;
+	}
+	if (p)
+		TAILQ_INSERT_AFTER(&pending_head, p, newentry, p_list);
+	else
+		TAILQ_INSERT_HEAD(&pending_head, newentry, p_list);
+}
+
+/* pending_lock must be locked */
 struct pending *
 pending_get(sa, salen, from, rcpt, date)  
 	struct sockaddr *sa;
@@ -170,13 +186,10 @@
 
 	pending->p_refcnt = 1;
 
-	pthread_mutex_lock(&pending_change_lock);
-	TAILQ_INSERT_TAIL(&pending_head, pending, p_list); 
+	pending_insert_to_queue(pending);
 	TAILQ_INSERT_TAIL(&pending_buckets[BUCKET_HASH(pending->p_sa, 
 	    from, rcpt, PENDING_BUCKETS)].b_pending_head, pending, pb_list); 
 
-	dump_dirty++;
-
 	(void)gettimeofday(&tv, NULL);
 
 	if (conf.c_debug) {
@@ -184,12 +197,12 @@
 		    pending->p_addr, pending->p_from, pending->p_rcpt, 
 		    pending->p_tv.tv_sec - tv.tv_sec);
 	}
-	pthread_mutex_unlock(&pending_change_lock);
+
 out:
 	return pending;
 }
 
-/* pending list should be write-locked & bucket must be locked */
+/* pending_lock must be locked */
 void
 pending_put(pending) 
 	struct pending *pending;
@@ -199,17 +212,13 @@
 		    pending->p_addr, pending->p_from, pending->p_rcpt);
 	}
 
-	pthread_mutex_lock(&pending_change_lock);
-	TAILQ_REMOVE(&pending_head, pending, p_list);	
+	TAILQ_REMOVE(&pending_head, pending, p_list);
 	TAILQ_REMOVE(&pending_buckets[BUCKET_HASH(pending->p_sa, 
 	    pending->p_from, pending->p_rcpt, PENDING_BUCKETS)].b_pending_head,
 	    pending, pb_list); 
 	
 	pending_free(pending);
 
-	dump_dirty++;
-	pthread_mutex_unlock(&pending_change_lock);
-
 	return;
 }
 
@@ -223,7 +232,7 @@
 	
 	gettimeofday(&now, NULL);
 	
-	PENDING_WRLOCK;
+	PENDING_LOCK;
 	for (pending = TAILQ_FIRST(&pending_head); pending; pending = next) {
 		next = TAILQ_NEXT(pending, p_list);
 		/*
@@ -237,7 +246,7 @@
 				    pending->p_addr, pending->p_from,
 				    pending->p_rcpt);
 			}
-			dirty = 1;
+			++dirty;
 			pending_put(pending);
 			continue;
 		}
@@ -261,14 +270,14 @@
 	struct pending *next;
 	struct timeval tv;
 	struct pending_bucket *b;
+	int dirty = 0;
 
 	gettimeofday(&tv, NULL);
 	if (!iptostring(sa, salen, addr, sizeof(addr)))
 		return;
 
-	PENDING_RDLOCK;
 	b = &pending_buckets[BUCKET_HASH(sa, from, rcpt, PENDING_BUCKETS)];
-	pthread_mutex_lock(&b->bucket_mtx);
+	PENDING_LOCK;
 	for (pending = TAILQ_FIRST(&b->b_pending_head); 
 	    pending; pending = next) {
 		next = TAILQ_NEXT(pending, pb_list);
@@ -281,14 +290,15 @@
 		    (strcmp(rcpt, pending->p_rcpt) == 0) &&
 		    (pending->p_tv.tv_sec == time)) {
 			pending_put(pending);
+			++dirty;
 			break;
 		}
 
 	}
-	pthread_mutex_unlock(&b->bucket_mtx);
 	PENDING_UNLOCK;
 	
-	pending_timeout();
+	dirty += pending_timeout();
+	dump_touch(dirty);
 	
 	return;
 }
@@ -322,9 +332,8 @@
 
 	dirty = pending_timeout();
 	
-	PENDING_RDLOCK;
 	b = &pending_buckets[BUCKET_HASH(sa, from, rcpt, PENDING_BUCKETS)];
-	pthread_mutex_lock(&b->bucket_mtx);
+	PENDING_LOCK;
 	for (pending = TAILQ_FIRST(&b->b_pending_head); 
 	    pending; pending = next) {
 		next = TAILQ_NEXT(pending, pb_list);
@@ -359,7 +368,7 @@
 				autowhite_add(sa, salen, from, rcpt, 
 				    &date, queueid);
 				rest = 0;
-				dirty = 1;
+				++dirty;
 			}
 
 			goto out;
@@ -372,12 +381,13 @@
 	 */
 	date = now + delay;
 	pending = pending_get(sa, salen, from, rcpt, date);
-	peer_create(pending); /* XXXmanu if pending == NULL? */
-	rest = pending->p_tv.tv_sec - now;
-	dirty = 1;
+	if (pending) {
+		++dirty;
+		peer_create(pending);
+		rest = pending->p_tv.tv_sec - now;
+	} /* otherwise return 1 and accept the mail */
 
 out:
-	pthread_mutex_unlock(&b->bucket_mtx);
 	PENDING_UNLOCK;
 
 	if (remaining != NULL)
@@ -386,8 +396,10 @@
 	if (elapsed != NULL)
 		*elapsed = now - (accepted - delay);
 
-	if (dirty)
+	if (dirty) {
+		dump_touch(dirty);
 		dump_flush();
+	}
 
 	if (rest <= 0)
 		return 1;
@@ -408,8 +420,7 @@
 	fprintf(stream, "# Sender IP\t%s\t%s\tTime accepted\n", 
 	    "Sender e-mail", "Recipient e-mail");
 
-	PENDING_RDLOCK;
-	pthread_mutex_lock(&pending_change_lock);
+	PENDING_LOCK;
 	TAILQ_FOREACH(pending, &pending_head, p_list) {
 		if (conf.c_dump_no_time_translation) {
 			fprintf(stream, "%s\t%s\t%s\t%ld\n", 
@@ -427,7 +438,6 @@
 		
 		done++;
 	}
-	pthread_mutex_unlock(&pending_change_lock);
 	PENDING_UNLOCK;
 
 	return done;
@@ -437,9 +447,9 @@
 pending_ref(pending)
 	struct pending *pending;
 {
-	WRLOCK(refcnt_lock);
+	pthread_mutex_lock(&refcnt_lock);
 	pending->p_refcnt++;
-	UNLOCK(refcnt_lock);
+	pthread_mutex_unlock(&refcnt_lock);
 	return pending;
 }
 
@@ -447,13 +457,13 @@
 pending_free(pending)
 	struct pending *pending;
 {
-	WRLOCK(refcnt_lock);
+	pthread_mutex_lock(&refcnt_lock);
 	pending->p_refcnt--;
 	if (pending->p_refcnt > 0) {
-		UNLOCK(refcnt_lock);
+		pthread_mutex_unlock(&refcnt_lock);
 		return;
 	}
-	UNLOCK(refcnt_lock);
+	pthread_mutex_unlock(&refcnt_lock);
 	free(pending->p_sa);
 	free(pending->p_addr);
 	free(pending->p_from);
@@ -657,7 +667,7 @@
 	if (!iptostring(sa, salen, addr, sizeof(addr)))
 		return;
 
-	PENDING_RDLOCK;
+	PENDING_LOCK;
 	for (pending = TAILQ_FIRST(&pending_head); pending; pending = next) {
 		next = TAILQ_NEXT(pending, p_list);
 
@@ -672,6 +682,7 @@
 
 	}
 	PENDING_UNLOCK;
+	dump_touch(count_pending);
 
 	/* And flush autowhite list as well... */
 	count_white = autowhite_del_addr(sa, salen);
diff -ru milter-greylist.maybeforged/pending.h milter-greylist.thread/pending.h
--- milter-greylist.maybeforged/pending.h	2006-08-20 05:53:26.000000000 +0000
+++ milter-greylist.thread/pending.h	2006-08-21 12:27:17.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: pending.h,v 1.36 2006/08/20 05:53:26 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -59,10 +60,6 @@
 
 #include "milter-greylist.h"
 
-#define PENDING_WRLOCK WRLOCK(pending_lock)
-#define PENDING_RDLOCK RDLOCK(pending_lock)
-#define PENDING_UNLOCK UNLOCK(pending_lock)
-
 TAILQ_HEAD(pendinglist, pending);
 
 struct pending {
@@ -78,12 +75,12 @@
 };
 
 struct pending_bucket {
-	pthread_mutex_t	bucket_mtx;
 	TAILQ_HEAD(, pending) b_pending_head;
 };
 
-extern pthread_rwlock_t pending_lock;
-extern pthread_mutex_t pending_change_lock;
+#define PENDING_LOCK pthread_mutex_lock(&pending_lock);
+#define PENDING_UNLOCK pthread_mutex_unlock(&pending_lock);
+extern pthread_mutex_t pending_lock;
 
 void pending_init(void);
 struct pending *pending_get(struct sockaddr *, socklen_t, char *, char *,
diff -ru milter-greylist.maybeforged/sync.c milter-greylist.thread/sync.c
--- milter-greylist.maybeforged/sync.c	2006-08-20 05:53:26.000000000 +0000
+++ milter-greylist.thread/sync.c	2006-08-21 05:36:01.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: sync.c,v 1.62 2006/08/20 05:53:26 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -66,12 +67,16 @@
 	int sock;
 };
 
+static pthread_mutex_t sync_master_lock = PTHREAD_MUTEX_INITIALIZER;
 struct sync_master_sock sync_master4 = { 0, -1 };
 struct sync_master_sock sync_master6 = { 0, -1 };
+
 struct peerlist peer_head;
 pthread_rwlock_t peer_lock; /* For the peer list */
-pthread_rwlock_t sync_lock; /* For all peer's sync queue */
-pthread_cond_t sync_sleepflag;
+
+static pthread_mutex_t sync_dirty_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sync_sleepflag = PTHREAD_COND_INITIALIZER;
+static int sync_dirty = 0;
 
 static void sync_listen(char *, char *, struct sync_master_sock *);
 static int local_addr(const struct sockaddr *sa, const socklen_t salen);
@@ -91,18 +96,6 @@
 		exit(EX_OSERR);
 	}
 
-	if ((error = pthread_rwlock_init(&sync_lock, NULL)) != 0) {
-		syslog(LOG_ERR, 
-		    "pthread_rwlock_init failed: %s", strerror(error));
-		exit(EX_OSERR);
-	}
-
-	if ((error = pthread_cond_init(&sync_sleepflag, NULL)) != 0) {
-		syslog(LOG_ERR, 
-		    "pthread_cond_init failed: %s", strerror(error));
-		exit(EX_OSERR);
-	}
-
 	return;
 }
 
@@ -123,6 +116,7 @@
 			fclose(peer->p_stream);
 
 		LIST_REMOVE(peer, p_list);
+		pthread_mutex_destroy(&peer->p_mtx);
 		free(peer->p_name);
 		free(peer);
 	}
@@ -148,6 +142,7 @@
 	peer->p_stream = NULL;
 	peer->p_flags = 0;
 	TAILQ_INIT(&peer->p_deferred);
+	pthread_mutex_init(&peer->p_mtx, NULL);
 
 	PEER_WRLOCK;
 	LIST_INSERT_HEAD(&peer_head, peer, p_list);
@@ -203,16 +198,16 @@
 	struct peer *peer;
 	struct sync *sync;
 {
-	SYNC_WRLOCK;
+	int r = 0;
+
+	pthread_mutex_lock(&peer->p_mtx);
 	if (peer->p_qlen < SYNC_MAXQLEN) {
 		TAILQ_INSERT_HEAD(&peer->p_deferred, sync, s_list);
 		peer->p_qlen++;
-		SYNC_UNLOCK;
-		return 1;
-	} else {
-		SYNC_UNLOCK;
-		return 0;
+		r = 1;
 	}
+	pthread_mutex_unlock(&peer->p_mtx);
+	return r;
 }
 
 static struct sync *
@@ -221,13 +216,13 @@
 {
 	struct sync *sync;
 
-	SYNC_WRLOCK;
+	pthread_mutex_lock(&peer->p_mtx);
 	sync = TAILQ_FIRST(&peer->p_deferred);
 	if (!TAILQ_EMPTY(&peer->p_deferred)) {
 		TAILQ_REMOVE(&peer->p_deferred, sync, s_list);
 		peer->p_qlen--;
 	}
-	SYNC_UNLOCK;
+	pthread_mutex_unlock(&peer->p_mtx);
 	return sync;
 }
 
@@ -599,8 +594,9 @@
 	empty = LIST_EMPTY(&peer_head);
 	PEER_UNLOCK;
 
+	pthread_mutex_lock(&sync_master_lock);
 	if (empty || sync_master4.runs || sync_master6.runs)
-		return;
+		goto last;
 
 	if (conf.c_syncaddr != NULL) {
 		if (strchr(conf.c_syncaddr, ':'))
@@ -653,6 +649,8 @@
 			exit(EX_OSERR);
 		}
 	}
+last:
+	pthread_mutex_unlock(&sync_master_lock);
 }
 
 void *
@@ -661,6 +659,7 @@
 {
 	struct sync_master_sock *sms = arg;
 
+	conf_retain();
 	for (;;) {
 		sockaddr_t raddr;
 		socklen_t raddrlen;
@@ -670,10 +669,17 @@
 		struct peer *peer;
 		char peerstr[IPADDRSTRLEN];
 		int error;
+		int sock;
 
+		pthread_mutex_lock(&sync_master_lock);
+		sock = sms->sock;
+		pthread_mutex_unlock(&sync_master_lock);
+
+		/* TODO: accept connections in nonblocking mode
+		 * in order to watch conf change */
 		bzero((void *)&raddr, sizeof(raddr));
 		raddrlen = sizeof(raddr);
-		if ((fd = accept(sms->sock, SA(&raddr), &raddrlen)) == -1) {
+		if ((fd = accept(sock, SA(&raddr), &raddrlen)) == -1) {
 			syslog(LOG_ERR, "incoming connexion "
 			    "failed: %s\n", strerror(errno));
 
@@ -683,6 +689,9 @@
 		}
 		unmappedaddr(SA(&raddr), &raddrlen);
 
+		conf_release();
+		conf_retain();
+
 		iptostring(SA(&raddr), raddrlen, peerstr, sizeof(peerstr));
 		syslog(LOG_INFO, "Incoming MX sync connexion from %s", 
 		    peerstr);
@@ -709,9 +718,12 @@
 
 			PEER_UNLOCK;
 			fclose(stream);
+			pthread_mutex_lock(&sync_master_lock);
 			close(sms->sock);
 			sms->sock = -1;
 			sms->runs = 0;
+			pthread_mutex_unlock(&sync_master_lock);
+			conf_release();
 			return NULL;
 		}
 			
@@ -788,6 +800,7 @@
 	return NULL;
 }
 
+/* sync_master_lock must be locked */
 static void
 sync_listen(addr, port, sms)
         char *addr, *port;
@@ -906,8 +919,11 @@
 	sockaddr_t addr;
 	socklen_t addrlen;
 	time_t date;
-	time_t aw = time(NULL) + conf.c_autowhite_validity;
+	time_t aw;
+	
+	conf_retain();
 
+	aw = time(NULL) + conf.c_autowhite_validity;
 	fprintf(stream, "200 Yeah, what do you want?\n");
 	fflush(stream);
 
@@ -1104,10 +1120,13 @@
 		fflush(stream);
 
 		if (action == PS_CREATE) {
-			PENDING_WRLOCK;
+			int dirty = 0;
+			PENDING_LOCK;
 			/* delay = -1 means unused: we supply the date */
-			pending_get(SA(&addr), addrlen, from, rcpt, date);
+			if (pending_get(SA(&addr), addrlen, from, rcpt, date))
+				++dirty;
 			PENDING_UNLOCK;
+			dump_touch(dirty);
 		}
 		if (action == PS_DELETE) {
 			pending_del(SA(&addr), addrlen, from, rcpt, date);
@@ -1126,6 +1145,8 @@
 	fprintf(stream, "202 Good bye\n");
 	fclose(stream);
 
+	conf_release();
+
 	return;
 
 }
@@ -1233,6 +1254,9 @@
 		sync_free(sync);
 	}
 
+	pthread_mutex_lock(&sync_dirty_lock);
+	sync_dirty = 1;
+	pthread_mutex_unlock(&sync_dirty_lock);
 	if ((error = pthread_cond_signal(&sync_sleepflag)) != 0) {
 		syslog(LOG_ERR, 
 		    "cannot wakeup sync_sender: %s", strerror(error));
@@ -1291,9 +1315,14 @@
 	}
 
 	for (;;) {
-		if ((error = pthread_cond_wait(&sync_sleepflag, &mutex)) != 0)
-			syslog(LOG_ERR, "pthread_cond_wait failed: %s\n", 
-			    strerror(error));
+		pthread_mutex_lock(&sync_dirty_lock);
+		while (!sync_dirty)
+			pthread_cond_wait(&sync_sleepflag, &sync_dirty_lock);
+		sync_dirty = 0;
+		pthread_mutex_unlock(&sync_dirty_lock);
+
+		conf_retain();
+
 		if (conf.c_debug) {
 			syslog(LOG_DEBUG, "sync_sender running");
 			gettimeofday(&tv1, NULL);
@@ -1334,6 +1363,8 @@
 			    "done %d entries in %ld.%06lds", done,
 			    tv3.tv_sec, tv3.tv_usec);
 		}
+
+		conf_release();
 	}
 }
 
diff -ru milter-greylist.maybeforged/sync.h milter-greylist.thread/sync.h
--- milter-greylist.maybeforged/sync.h	2006-08-08 13:54:41.000000000 +0000
+++ milter-greylist.thread/sync.h	2006-08-20 22:38:59.000000000 +0000
@@ -1,4 +1,5 @@
 /* $Id: sync.h,v 1.18 2006/08/08 13:54:41 manu Exp $ */
+/* vim: set sw=8 ts=8 sts=8 noet cino=(0: */
 
 /*
  * Copyright (c) 2004 Emmanuel Dreyfus
@@ -55,10 +56,6 @@
 #define PEER_RDLOCK RDLOCK(peer_lock);
 #define PEER_UNLOCK UNLOCK(peer_lock);
 
-#define SYNC_WRLOCK WRLOCK(sync_lock);
-#define SYNC_RDLOCK RDLOCK(sync_lock);
-#define SYNC_UNLOCK UNLOCK(sync_lock);
-
 LIST_HEAD(peerlist, peer);
 TAILQ_HEAD(synclist, sync);
 
@@ -66,6 +63,9 @@
 	char *p_name;
 	FILE *p_stream;
 	int p_socket;
+	/* p_mtx protects p_deferred and p_qlen.
+	 * peer list must be read or rw locked before. */
+	pthread_mutex_t p_mtx;
 	struct synclist p_deferred;
 	LIST_ENTRY(peer) p_list;
 	unsigned int p_qlen;
