changeset 843:2ea56ea4bd34

Add ability to subscribe to 'both' and avoid a deadlock when switching. - 'both' means normal and digest versions simultaneously; information about this feature is not included in the default list texts, but a few power users find it helpful. - Only take out locks when actually performing a subscription, not before checking whether the address is already subscribed; we only really need it when we are actually changing the file, and there is a potential deadlock which could occur while waiting for unsubscription to complete as part of a switch if the lock is taken earlier. - Also moved code which sends unsubscription confirmations so that it only runs once, after unsubsciption has been completed, not every time the address is removed; this is only really important when 'both' is a realistic subscription option, but it could avoid other double-sends as well.
author Ben Schmidt
date Wed, 25 Jan 2012 22:34:57 +1100
parents c6fe438f3e60
children 58d726e86650
files ChangeLog README.listtexts include/mlmmj.h include/subscriberfuncs.h src/listcontrol.c src/mlmmj-bounce.c src/mlmmj-process.c src/mlmmj-sub.c src/mlmmj-unsub.c src/subscriberfuncs.c
diffstat 10 files changed, 311 insertions(+), 178 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Jan 24 04:08:24 2012 +1100
+++ b/ChangeLog	Wed Jan 25 22:34:57 2012 +1100
@@ -1,3 +1,4 @@
+ o Add ability to subscribe to both (normal and digest).
  o Fix access logic so subonlypost doesn't override a send access rule.
  o Make +unsubscribe remove the requester from all versions of the list.
  o Make mlmmj-unsub default to removing the requester from all versions of the
--- a/README.listtexts	Tue Jan 24 04:08:24 2012 +1100
+++ b/README.listtexts	Wed Jan 25 22:34:57 2012 +1100
@@ -79,9 +79,9 @@
   sent to a person requesting subscription when they need to wait for
   gatekeeping for permission to join
 
-- deny-sub-disabled-digest (sub-deny-digest)
+- deny-sub-disabled-{digest|both} (sub-deny-digest)
 - deny-sub-disabled-nomail (sub-deny-nomail)
-- deny-sub-subbed-{normal|digest|nomail} (sub-subscribed)
+- deny-sub-subbed-{normal|digest|nomail|both} (sub-subscribed)
 - deny-sub-closed *
 - deny-sub-expired *
 - deny-sub-obstruct *
--- a/include/mlmmj.h	Tue Jan 24 04:08:24 2012 +1100
+++ b/include/mlmmj.h	Wed Jan 25 22:34:57 2012 +1100
@@ -77,10 +77,11 @@
 	SUB_NOMAIL,
 	SUB_FILE, /* For single files (moderator, owner etc.) */
 	SUB_ALL, /* For listing or unsubscribing all kinds of subscribers */
+	SUB_BOTH, /* For normal+digest subscription */
 	SUB_NONE /* For when an address is not subscribed at all */
 };
 
-char *subtype_strs[6]; /* count matches enum above; defined in subscriberfuncs.c */
+char *subtype_strs[7]; /* count matches enum above; defined in subscriberfuncs.c */
 
 enum subreason {
 	SUB_REQUEST,
--- a/include/subscriberfuncs.h	Tue Jan 24 04:08:24 2012 +1100
+++ b/include/subscriberfuncs.h	Wed Jan 25 22:34:57 2012 +1100
@@ -26,6 +26,6 @@
 
 off_t find_subscriber(int fd, const char *address);
 int is_subbed_in(const char *subddirname, const char *address);
-enum subtype is_subbed(const char *listdir, const char *address);
+enum subtype is_subbed(const char *listdir, const char *address, int both);
 
 #endif /* SUBSCRIBERFUNC_H */
--- a/src/listcontrol.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/listcontrol.c	Wed Jan 25 22:34:57 2012 +1100
@@ -51,9 +51,11 @@
 enum ctrl_e {
 	CTRL_SUBSCRIBE_DIGEST,
 	CTRL_SUBSCRIBE_NOMAIL,
+	CTRL_SUBSCRIBE_BOTH,
 	CTRL_SUBSCRIBE,
 	CTRL_CONFSUB_DIGEST,
 	CTRL_CONFSUB_NOMAIL,
+	CTRL_CONFSUB_BOTH,
 	CTRL_CONFSUB,
 	CTRL_UNSUBSCRIBE_DIGEST,
 	CTRL_UNSUBSCRIBE_NOMAIL,
@@ -85,9 +87,11 @@
 static struct ctrl_command ctrl_commands[] = {
 	{ "subscribe-digest",   0 },
 	{ "subscribe-nomail",   0 },
+	{ "subscribe-both",     0 },
 	{ "subscribe",          0 },
 	{ "confsub-digest",     1 },
 	{ "confsub-nomail",     1 },
+	{ "confsub-both",       1 },
 	{ "confsub",            1 },
 	{ "unsubscribe-digest", 0 },
 	{ "unsubscribe-nomail", 0 },
@@ -296,6 +300,55 @@
 		exit(EXIT_FAILURE);
 		break;
 
+	/* listname+subscribe-both@domain.tld */
+	case CTRL_SUBSCRIBE_BOTH:
+		if (closedlist || closedlistsub) {
+			errno = 0;
+			log_error(LOG_ARGS, "A subscribe-both request was"
+				" sent to a closed list. Ignoring mail");
+			return -1;
+		}
+		if (!strchr(fromemails->emaillist[0], '@')) {
+			/* Not a valid From: address */
+			errno = 0;
+			log_error(LOG_ARGS, "A subscribe-both request was"
+				" sent with an invalid From: header."
+				" Ignoring mail");
+			return -1;
+		}
+		if (statctrl(listdir, "nodigestsub")) {
+			errno = 0;
+			log_error(LOG_ARGS, "A subscribe-both request was"
+				" denied");
+			txt = open_text(listdir, "deny", "sub", "disabled",
+					"both", "sub-deny-digest");
+			MY_ASSERT(txt);
+			register_unformatted(txt, "subaddr",
+					fromemails->emaillist[0]);
+			queuefilename = prepstdreply(txt, listdir,
+					"$listowner$",
+					fromemails->emaillist[0], NULL);
+			MY_ASSERT(queuefilename);
+			close_text(txt);
+			send_help(listdir, queuefilename,
+					fromemails->emaillist[0], mlmmjsend);
+			return -1;
+		}
+		log_oper(listdir, OPLOGFNAME, "mlmmj-sub: request for both"
+					" subscription from %s",
+					fromemails->emaillist[0]);
+		execlp(mlmmjsub, mlmmjsub,
+				"-L", listdir,
+				"-a", fromemails->emaillist[0],
+				"-b",
+				"-r", "-c",
+				(nosubconfirm ? (char *)NULL : "-C"),
+				(char *)NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+					mlmmjsub);
+		exit(EXIT_FAILURE);
+		break;
+
 	/* listname+subscribe@domain.tld */
 	case CTRL_SUBSCRIBE:
 		if (closedlist || closedlistsub) {
@@ -382,6 +435,34 @@
 		exit(EXIT_FAILURE);
 		break;
 
+	/* listname+subconf-both-COOKIE@domain.tld */
+	case CTRL_CONFSUB_BOTH:
+		conffilename = concatstr(3, listdir, "/subconf/", param);
+		myfree(param);
+		if((tmpfd = open(conffilename, O_RDONLY)) < 0) {
+			/* invalid COOKIE */
+			errno = 0;
+			log_error(LOG_ARGS, "A subconf-both request was"
+				" sent with a mismatching cookie."
+				" Ignoring mail");
+			return -1;
+		}
+		tmpstr = mygetline(tmpfd);
+		chomp(tmpstr);
+		close(tmpfd);
+		unlink(conffilename);
+		log_oper(listdir, OPLOGFNAME, "mlmmj-sub: %s confirmed"
+					" subscription to both", tmpstr);
+		execlp(mlmmjsub, mlmmjsub,
+				"-L", listdir,
+				"-a", tmpstr,
+				"-b",
+				"-R", "-c", (char *)NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+				mlmmjsub);
+		exit(EXIT_FAILURE);
+		break;
+
 	/* listname+subconf-COOKIE@domain.tld */
 	case CTRL_CONFSUB:
 		conffilename = concatstr(3, listdir, "/subconf/", param);
@@ -763,7 +844,7 @@
 		}
 		subonlyget = statctrl(listdir, "subonlyget");
 		if(subonlyget) {
-			if(is_subbed(listdir, fromemails->emaillist[0]) ==
+			if(is_subbed(listdir, fromemails->emaillist[0], 0) ==
 					SUB_NONE) {
 				errno = 0;
 				log_error(LOG_ARGS, "A get request was sent"
--- a/src/mlmmj-bounce.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/mlmmj-bounce.c	Wed Jan 25 22:34:57 2012 +1100
@@ -342,7 +342,7 @@
 	*a = '@';
 
 	/* make sure it's a subscribed address */
-	if(is_subbed(listdir, address) == SUB_NONE) {
+	if(is_subbed(listdir, address, 0) == SUB_NONE) {
 		log_error(LOG_ARGS, "%s is bouncing but not subscribed?",
 				address);
 		if(mailname)
--- a/src/mlmmj-process.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/mlmmj-process.c	Wed Jan 25 22:34:57 2012 +1100
@@ -984,7 +984,7 @@
 			myfree(donemailname);
 			exit(EXIT_SUCCESS);
 		}
-		if(is_subbed(listdir, posteraddr) == SUB_NONE) {
+		if(is_subbed(listdir, posteraddr, 0) == SUB_NONE) {
 			if(statctrl(listdir, "modnonsubposts")) {
 				moderated = 1;
 				modreason = MODNONSUBPOSTS;
--- a/src/mlmmj-sub.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/mlmmj-sub.c	Wed Jan 25 22:34:57 2012 +1100
@@ -75,6 +75,9 @@
 		case SUB_NOMAIL:
 			str = concatstr(4, subaddr, "\n", "SUB_NOMAIL", "\n");
 			break;
+		case SUB_BOTH:
+			str = concatstr(4, subaddr, "\n", "SUB_BOTH", "\n");
+			break;
 	}
 	
 	for (;;) {
@@ -270,6 +273,11 @@
 		goto freedone;
 	}
 
+	if(strncmp(readtype, "SUB_BOTH", 8) == 0) {
+		*subtypeptr = SUB_BOTH;
+		goto freedone;
+	}
+
 	log_error(LOG_ARGS, "Type %s not valid in %s", readtype,
 			modfilename);
 
@@ -305,6 +313,10 @@
 		case SUB_NOMAIL:
 			listtext = mystrdup("sub-ok-nomail");
 			break;
+		case SUB_BOTH:
+			/* No legacy list text as feature didn't exist. */
+			listtext = mystrdup("sub-ok");
+			break;
 	}
 
 	txt = open_text(listdir, "finish", "sub",
@@ -356,6 +368,10 @@
 		case SUB_NOMAIL:
 			listtext = mystrdup("notifysub-nomail");
 			break;
+		case SUB_BOTH:
+			/* No legacy list text as feature didn't exist. */
+			listtext = mystrdup("notifysub");
+			break;
 	}
 
 	txt = open_text(listdir, "notify", "sub",
@@ -441,6 +457,11 @@
 			listtext = mystrdup("sub-confirm-nomail");
 			tmpstr = mystrdup("confsub-nomail-");
 			break;
+		case SUB_BOTH:
+			/* No legacy list text as feature didn't exist. */
+			listtext = mystrdup("sub-confirm");
+			tmpstr = mystrdup("confsub-both-");
+			break;
 	}
 
 	confirmaddr = concatstr(6, listname, listdelim, tmpstr, randomstr, "@",
@@ -537,19 +558,100 @@
 	exit(EXIT_FAILURE);
 }
 
+static void subscribe_type(char *listdir, char *listaddr, char *listdelim,
+		char *address, char *mlmmjsend,
+		enum subtype typesub, enum subreason reasonsub) {
+	char *subfilename = NULL;
+	char chstr[2], *subdir;
+	char *subddirname = NULL, *sublockname;
+	int groupwritable = 0, sublock, sublockfd, lock, subfilefd;
+	struct stat st;
+	size_t len;
+
+	switch(typesub) {
+		default:
+		case SUB_NORMAL:
+			subdir = "/subscribers.d/";
+			break;
+		case SUB_DIGEST:
+			subdir = "/digesters.d/";
+			break;
+		case SUB_NOMAIL:
+			subdir = "/nomailsubs.d/";
+			break;
+	}
+
+	subddirname = concatstr(2, listdir, subdir);
+	if (stat(subddirname, &st) == 0) {
+		if(st.st_mode & S_IWGRP) {
+			groupwritable = S_IRGRP|S_IWGRP;
+			umask(S_IWOTH);
+			setgid(st.st_gid);
+		}
+	}
+
+	chstr[0] = address[0];
+	chstr[1] = '\0';
+	
+	subfilename = concatstr(3, listdir, subdir, chstr);
+
+	sublockname = concatstr(5, listdir, subdir, ".", chstr, ".lock");
+	sublockfd = open(sublockname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+	if(sublockfd < 0) {
+		log_error(LOG_ARGS, "Error opening lock file %s",
+				sublockname);
+		myfree(sublockname);
+		exit(EXIT_FAILURE);
+	}
+
+	sublock = myexcllock(sublockfd);
+	if(sublock < 0) {
+		log_error(LOG_ARGS, "Error locking '%s' file",
+				sublockname);
+		myfree(sublockname);
+		close(sublockfd);
+		exit(EXIT_FAILURE);
+	}
+
+	subfilefd = open(subfilename, O_RDWR|O_CREAT,
+				S_IRUSR|S_IWUSR|groupwritable);
+	if(subfilefd == -1) {
+		log_error(LOG_ARGS, "Could not open '%s'", subfilename);
+		myfree(sublockname);
+		exit(EXIT_FAILURE);
+	}
+
+	lock = myexcllock(subfilefd);
+	if(lock) {
+		log_error(LOG_ARGS, "Error locking subscriber file");
+		close(subfilefd);
+		close(sublockfd);
+		myfree(sublockname);
+		exit(EXIT_FAILURE);
+	}
+
+	lseek(subfilefd, 0L, SEEK_END);
+	len = strlen(address);
+	address[len] = '\n';
+	writen(subfilefd, address, len + 1);
+	address[len] = 0;
+	close(subfilefd);
+	close(sublockfd);
+	unlink(sublockname);
+	myfree(sublockname);
+}
+
 int main(int argc, char **argv)
 {
-	char *listaddr, *listdelim, *listdir = NULL, *address = NULL;
-	char *subfilename = NULL, *mlmmjsend, *mlmmjunsub, *bindir;
-	char chstr[2], *subdir;
-	char *subddirname = NULL, *sublockname, *lowcaseaddr;
-	char *modstr = NULL;
-	int subconfirm = 0, confirmsub = 0, opt, subfilefd, lock, notifysub;
-	int changeuid = 1, status, digest = 0, nomail = 0, i = 0, submod = 0;
-	int groupwritable = 0, sublock, sublockfd, nogensubscribed = 0;
-	int force = 0, quiet = 0;
+	char *listaddr, *listdelim, *listdir = NULL;
+	char *mlmmjsend, *mlmmjunsub, *bindir;
+	char *address = NULL, *lowcaseaddr, *modstr = NULL;
+	const char *flag = NULL;
+	int opt, subconfirm = 0, confirmsub = 0, notifysub;
+	int changeuid = 1, status, digest = 0, nomail = 0, both = 0;
+	int nogensubscribed = 0;
+	int force = 0, quiet = 0, i = 0;
 	enum subtype subbed;
-	size_t len;
 	struct stat st;
 	pid_t pid, childpid = 0;
 	uid_t uid;
@@ -565,11 +667,14 @@
 	mlmmjunsub = concatstr(2, bindir, "/mlmmj-unsub");
 	myfree(bindir);
 
-	while ((opt = getopt(argc, argv, "hcCdfm:nsVUL:a:qrR")) != -1) {
+	while ((opt = getopt(argc, argv, "hbcCdfm:nsVUL:a:qrR")) != -1) {
 		switch(opt) {
 		case 'a':
 			address = optarg;
 			break;
+		case 'b':
+			both = 1;
+			break;
 		case 'c':
 			confirmsub = 1;
 			break;
@@ -627,6 +732,25 @@
 		exit(EXIT_FAILURE);
 	}
 
+	if(both + digest + nomail > 1) {
+		fprintf(stderr, "Specify at most one of -b, -d and -n\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if(digest)
+		typesub = SUB_DIGEST;
+	if(nomail)
+		typesub = SUB_NOMAIL;
+	if(both)
+		typesub = SUB_BOTH;
+
+	if(reasonsub == SUB_CONFIRM && subconfirm) {
+		fprintf(stderr, "Cannot specify both -C and -R\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
 	if(modstr) {
 		getaddrandtype(listdir, modstr, &address, &typesub);
 		reasonsub = SUB_PERMIT;
@@ -638,23 +762,6 @@
 		exit(EXIT_SUCCESS);
 	}
 
-	if(digest && nomail) {
-		fprintf(stderr, "Specify at most one of -d and -n\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	if(digest)
-		typesub = SUB_DIGEST;
-	if(nomail)
-		typesub = SUB_NOMAIL;
-
-	if(reasonsub == SUB_CONFIRM && subconfirm) {
-		fprintf(stderr, "Cannot specify both -C and -R\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
 	/* Make the address lowercase */
 	lowcaseaddr = mystrdup(address);
 	i = 0;
@@ -671,28 +778,6 @@
 		exit(EXIT_SUCCESS);  /* XXX is this success? */
 	}
 
-	switch(typesub) {
-		default:
-		case SUB_NORMAL:
-			subdir = "/subscribers.d/";
-			break;
-		case SUB_DIGEST:
-			subdir = "/digesters.d/";
-			break;
-		case SUB_NOMAIL:
-			subdir = "/nomailsubs.d/";
-			break;
-	}
-
-	subddirname = concatstr(2, listdir, subdir);
-	if (stat(subddirname, &st) == 0) {
-		if(st.st_mode & S_IWGRP) {
-			groupwritable = S_IRGRP|S_IWGRP;
-			umask(S_IWOTH);
-			setgid(st.st_gid);
-		}
-	}
-
 	if(changeuid) {
 		uid = getuid();
 		if(!uid && stat(listdir, &st) == 0) {
@@ -706,76 +791,39 @@
 		}
 	}
 
-	chstr[0] = address[0];
-	chstr[1] = '\0';
-	
-	subfilename = concatstr(3, listdir, subdir, chstr);
-
-	sublockname = concatstr(5, listdir, subdir, ".", chstr, ".lock");
-	sublockfd = open(sublockname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
-	if(sublockfd < 0) {
-		log_error(LOG_ARGS, "Error opening lock file %s",
-				sublockname);
-		myfree(sublockname);
-		exit(EXIT_FAILURE);
-	}
-
-	sublock = myexcllock(sublockfd);
-	if(sublock < 0) {
-		log_error(LOG_ARGS, "Error locking '%s' file",
-				sublockname);
-		myfree(sublockname);
-		close(sublockfd);
-		exit(EXIT_FAILURE);
-	}
-
-	subfilefd = open(subfilename, O_RDWR|O_CREAT,
-				S_IRUSR|S_IWUSR|groupwritable);
-	if(subfilefd == -1) {
-		log_error(LOG_ARGS, "Could not open '%s'", subfilename);
-		myfree(sublockname);
-		exit(EXIT_FAILURE);
-	}
-
-	lock = myexcllock(subfilefd);
-	if(lock) {
-		log_error(LOG_ARGS, "Error locking subscriber file");
-		close(subfilefd);
-		close(sublockfd);
-		myfree(sublockname);
-		exit(EXIT_FAILURE);
-	}
-	subbed = is_subbed(listdir, address);
-	listdelim = getlistdelim(listdir);
+	subbed = is_subbed(listdir, address, 1);
 	
 	if(subbed == typesub) {
-		close(subfilefd);
-		myfree(subfilename);
-		close(sublockfd);
-		unlink(sublockname);
-		myfree(sublockname);
-
 		if(!nogensubscribed)
 			generate_subscribed(listdir, address, mlmmjsend,
 					typesub);
-		
 		return EXIT_SUCCESS;
 	} else if(subbed != SUB_NONE) {
 		reasonsub = SUB_SWITCH;
+		/* If we want to subscribe to both, we can just subscribe the
+		 * missing version, so don't unsub. */
+		if (!(typesub == SUB_BOTH &&
+				subbed != SUB_NOMAIL)) {
 		childpid = fork();
-		if(childpid < 0)
+			if(childpid < 0) {
 				log_error(LOG_ARGS, "Could not fork; "
 				"not unsubscribed from current version");
+			}
 		if (childpid == 0) {
+				if (subbed == SUB_BOTH) {
+					if (typesub == SUB_NORMAL) flag = "-d";
+					if (typesub == SUB_DIGEST) flag = "-N";
+				}
 			execlp(mlmmjunsub, mlmmjunsub,
 					"-L", listdir, "-q",
-					"-a", address,
+						"-a", address, flag,
 					(char *)NULL);
 			log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjunsub);
 			exit(EXIT_FAILURE);
 		}
 	}
+	}
 
 	if(childpid > 0) {
 		do /* Parent waits for the child */
@@ -783,38 +831,31 @@
 		while(pid == -1 && errno == EINTR);
 	}
 
-	if(subbed == SUB_NONE && subconfirm) {
-		close(subfilefd);
-		close(sublockfd);
-		unlink(sublockname);
-		myfree(sublockname);
+	listdelim = getlistdelim(listdir);
+
+	if(subbed == SUB_NONE && subconfirm)
 		generate_subconfirm(listdir, listaddr, listdelim,
 				    address, mlmmjsend, typesub, reasonsub);
-	} else {
-		if(modstr == NULL)
-				submod = subbed == SUB_NONE && !force &&
-				statctrl(listdir, "submod");
-		if(submod) {
-			close(subfilefd);
-			close(sublockfd);
-			unlink(sublockname);
-			myfree(sublockname);
+
+	if(modstr == NULL && subbed == SUB_NONE && !force &&
+			statctrl(listdir, "submod")) {
 			moderate_sub(listdir, listaddr, listdelim,
 				address, mlmmjsend, typesub, reasonsub);
 		}
-		lseek(subfilefd, 0L, SEEK_END);
-		len = strlen(address);
-		address[len] = '\n';
-		writen(subfilefd, address, len + 1);
-		address[len] = 0;
-		close(subfilefd);
-		close(sublockfd);
-		unlink(sublockname);
+
+	if (typesub == SUB_BOTH) {
+		if (subbed != SUB_NORMAL) {
+			subscribe_type(listdir, listaddr, listdelim, address,
+					mlmmjsend, SUB_NORMAL, reasonsub);
 	}
-
-	close(sublockfd);
-	unlink(sublockname);
-	myfree(sublockname);
+		if (subbed != SUB_DIGEST) {
+			subscribe_type(listdir, listaddr, listdelim, address,
+					mlmmjsend, SUB_DIGEST, reasonsub);
+		}
+	} else if (!(subbed == SUB_BOTH && typesub != SUB_NOMAIL)) {
+		subscribe_type(listdir, listaddr, listdelim, address,
+				mlmmjsend, typesub, reasonsub);
+	}
 
 	if(confirmsub) {
 		childpid = fork();
--- a/src/mlmmj-unsub.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/mlmmj-unsub.c	Wed Jan 25 22:34:57 2012 +1100
@@ -353,19 +353,18 @@
 }
 
 static void unsubscribe_type(char *listdir, char *listaddr, char *listdelim,
-		char *address, char *mlmmjsend, int confirmunsub,
+		char *address, char *mlmmjsend,
 		enum subtype typesub, enum subreason reasonsub) {
 	char *subdir, *subddirname, *sublockname;
 	char *subreadname = NULL, *subwritename;
 	int subread, subwrite, rlock, wlock;
 	int sublock, sublockfd;
 	int groupwritable = 0;
-	int unsubres, status;
+	int unsubres;
 	struct stat st;
 	DIR *subddir;
 	struct dirent *dp;
 	off_t suboff;
-	pid_t pid, childpid;
 
 	switch(typesub) {
 		default:
@@ -514,34 +513,11 @@
 		unlink(sublockname);
 		myfree(sublockname);
 
-		if(confirmunsub) {
-			childpid = fork();
-
-			if(childpid < 0) {
-				log_error(LOG_ARGS, "Could not fork");
-				confirm_unsub(listdir, listaddr, listdelim,
-						address, mlmmjsend,
-						typesub, reasonsub);
-			}
-
-			if(childpid > 0) {
-				do /* Parent waits for the child */
-					pid = waitpid(childpid, &status, 0);
-				while(pid == -1 && errno == EINTR);
-			}
-
-			/* child confirms subscription */
-			if(childpid == 0)
-				confirm_unsub(listdir, listaddr, listdelim,
-						address, mlmmjsend,
-						typesub, reasonsub);
-		}
         }
 
 	closedir(subddir);
 }
 
-
 int main(int argc, char **argv)
 {
 	int opt;
@@ -549,6 +525,7 @@
 	int confirmunsub = 0, unsubconfirm = 0, notifysub = 0;
 	int changeuid = 1, quiet = 0;
 	int nogennotsubscribed = 0, i = 0;
+	int status;
 	char *listaddr, *listdelim, *listdir = NULL, *address = NULL;
 	char *mlmmjsend, *bindir, *subdir, *subddirname;
 	char *lowcaseaddr;
@@ -556,6 +533,7 @@
 	enum subreason reasonsub = SUB_ADMIN;
 	uid_t uid;
 	struct stat st;
+	pid_t pid, childpid;
 
 	CHECKFULLPATH(argv[0]);
 	
@@ -678,7 +656,7 @@
 	}
 
 	if (typesub == SUB_ALL) {
-		subbed = is_subbed(listdir, address) != SUB_NONE;
+		subbed = is_subbed(listdir, address, 0) != SUB_NONE;
 	} else {
 		switch(typesub) {
 			default:
@@ -716,14 +694,37 @@
 
 	if (typesub == SUB_ALL) {
 		unsubscribe_type(listdir, listaddr, listdelim, address,
-				mlmmjsend, confirmunsub, SUB_NORMAL, reasonsub);
+				mlmmjsend, SUB_NORMAL, reasonsub);
 		unsubscribe_type(listdir, listaddr, listdelim, address,
-				mlmmjsend, confirmunsub, SUB_DIGEST, reasonsub);
+				mlmmjsend, SUB_DIGEST, reasonsub);
 		unsubscribe_type(listdir, listaddr, listdelim, address,
-				mlmmjsend, confirmunsub, SUB_NOMAIL, reasonsub);
+				mlmmjsend, SUB_NOMAIL, reasonsub);
 	} else {
 		unsubscribe_type(listdir, listaddr, listdelim, address,
-				mlmmjsend, confirmunsub, typesub, reasonsub);
+				mlmmjsend, typesub, reasonsub);
+	}
+
+	if(confirmunsub) {
+		childpid = fork();
+
+		if(childpid < 0) {
+			log_error(LOG_ARGS, "Could not fork");
+			confirm_unsub(listdir, listaddr, listdelim,
+					address, mlmmjsend,
+					typesub, reasonsub);
+		}
+
+		if(childpid > 0) {
+			do /* Parent waits for the child */
+				pid = waitpid(childpid, &status, 0);
+			while(pid == -1 && errno == EINTR);
+		}
+
+		/* child confirms subscription */
+		if(childpid == 0)
+			confirm_unsub(listdir, listaddr, listdelim,
+					address, mlmmjsend,
+					typesub, reasonsub);
 	}
 
         notifysub = !quiet && statctrl(listdir, "notifysub");
--- a/src/subscriberfuncs.c	Tue Jan 24 04:08:24 2012 +1100
+++ b/src/subscriberfuncs.c	Wed Jan 25 22:34:57 2012 +1100
@@ -46,6 +46,7 @@
 	"nomail",
 	"file",
 	"all",
+	"both",
 	"none"
 };
 
@@ -151,20 +152,27 @@
 	return retval;
 }
 
-enum subtype is_subbed(const char *listdir, const char *address)
+enum subtype is_subbed(const char *listdir, const char *address, int both)
 {
 	int retval;
 	char *subddirname;
+	enum subtype typesub = SUB_NONE;
 	
 	subddirname = concatstr(2, listdir, "/subscribers.d/");
 	retval = is_subbed_in(subddirname, address);
 	myfree(subddirname);
-	if (retval) return SUB_NORMAL;
+	if (retval) {
+		if (!both) return SUB_NORMAL;
+		typesub = SUB_NORMAL;
+	}
 
 	subddirname = concatstr(2, listdir, "/digesters.d/");
 	retval = is_subbed_in(subddirname, address);
 	myfree(subddirname);
-	if (retval) return SUB_DIGEST;
+	if (retval) {
+		if (typesub == SUB_NORMAL) return SUB_BOTH;
+		return SUB_DIGEST;
+	}
 
 	subddirname = concatstr(2, listdir, "/nomailsubs.d/");
 	retval = is_subbed_in(subddirname, address);