changeset 840:0f8242ffa2d0

Changes to how subscription and unsubscribe work. - Make +unsubscribe remove the requester from all versions of the list. - Make mlmmj-unsub default to removing the requester from all versions of the list. - Make mlmmj-sub and +subscribe[-digest|-nomail] switch existing subscriptions. - Add a switch to bypass notifying the owner on subscribe/unsubscribe. - Make type available in finish list texts.
author Ben Schmidt
date Tue, 24 Jan 2012 03:02:00 +1100
parents 605ce8682e9b
children fabf3a96132f
files ChangeLog README.listtexts include/mlmmj.h include/subscriberfuncs.h man/mlmmj-sub.1 man/mlmmj-unsub.1 src/listcontrol.c src/mlmmj-bounce.c src/mlmmj-process.c src/mlmmj-sub.c src/mlmmj-unsub.c src/subscriberfuncs.c
diffstat 12 files changed, 371 insertions(+), 314 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Jan 24 14:20:58 2012 +1100
+++ b/ChangeLog	Tue Jan 24 03:02:00 2012 +1100
@@ -1,3 +1,9 @@
+ 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
+   list.
+ o Make mlmmj-sub and +subscribe[-digest|-nomail] switch existing
+   subscriptions.
+ o Add a switch to bypass notifying the owner on subscribe/unsubscribe.
  o Introduce \<space> to indicate line-break positions to enable sensible
    wrapping of Chinese and similar text.
  o Allow lines to be longer than the wrapping width if there are no spaces,
--- a/README.listtexts	Tue Jan 24 14:20:58 2012 +1100
+++ b/README.listtexts	Tue Jan 24 03:02:00 2012 +1100
@@ -81,11 +81,11 @@
 
 - deny-sub-disabled-digest (sub-deny-digest)
 - deny-sub-disabled-nomail (sub-deny-nomail)
-- deny-sub-subbed (sub-subscribed)
+- deny-sub-subbed-{normal|digest|nomail} (sub-subscribed)
 - deny-sub-closed *
 - deny-sub-expired *
 - deny-sub-obstruct *
-- deny-unsub-unsubbed (unsub-notsubscribed)
+- deny-unsub-unsubbed-{normal|digest|nomail|all} (unsub-notsubscribed)
 - deny-post-subonlypost (subonlypost)
 - deny-post-access (access)
 - deny-post-maxmailsize (maxmailsize)
@@ -106,9 +106,9 @@
   if the rejection fails; but deny-post-reject will go to the person requesting
   the post if the rejection succeeds, causing the post to fail)
 
-- finish-sub-{request|confirm|admin|permit}-normal (sub-ok)
-- finish-sub-{request|confirm|admin|permit}-digest (sub-ok-digest)
-- finish-sub-{request|confirm|admin|permit}-nomail (sub-ok-nomail)
+- finish-sub-{request|confirm|admin|permit|switch}-normal (sub-ok)
+- finish-sub-{request|confirm|admin|permit|switch}-digest (sub-ok-digest)
+- finish-sub-{request|confirm|admin|permit|switch}-nomail (sub-ok-nomail)
 - finish-unsub-{request|confirm|admin}-normal (unsub-ok)
 - finish-unsub-{request|confirm|admin}-digest (unsub-ok-digest)
 - finish-unsub-{request|confirm|admin}-nomail (unsub-ok-nomail)
--- a/include/mlmmj.h	Tue Jan 24 14:20:58 2012 +1100
+++ b/include/mlmmj.h	Tue Jan 24 03:02:00 2012 +1100
@@ -76,20 +76,22 @@
 	SUB_DIGEST,
 	SUB_NOMAIL,
 	SUB_FILE, /* For single files (moderator, owner etc.) */
-	SUB_ALL /* For listing all kinds of subscribers */
+	SUB_ALL, /* For listing or unsubscribing all kinds of subscribers */
+	SUB_NONE /* For when an address is not subscribed at all */
 };
 
-char *subtype_strs[5]; /* count matches enum above; defined in mlmmj-sub.c */
+char *subtype_strs[6]; /* count matches enum above; defined in mlmmj-sub.c */
 
 enum subreason {
 	SUB_REQUEST,
 	SUB_CONFIRM,
 	SUB_PERMIT,
 	SUB_ADMIN,
-	SUB_BOUNCING
+	SUB_BOUNCING,
+	SUB_SWITCH
 };
 
-char * subreason_strs[5]; /* count matches enum above; defined in mlmmj-sub.c */
+char * subreason_strs[6]; /* count matches enum above; defined in mlmmj-sub.c */
 
 void print_version(const char *prg);
 
--- a/include/subscriberfuncs.h	Tue Jan 24 14:20:58 2012 +1100
+++ b/include/subscriberfuncs.h	Tue Jan 24 03:02:00 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);
-int is_subbed(const char *listdir, const char *address);
+enum subtype is_subbed(const char *listdir, const char *address);
 
 #endif /* SUBSCRIBERFUNC_H */
--- a/man/mlmmj-sub.1	Tue Jan 24 14:20:58 2012 +1100
+++ b/man/mlmmj-sub.1	Tue Jan 24 03:02:00 2012 +1100
@@ -4,13 +4,13 @@
 .SH SYNOPSIS
 .B mlmmj-sub
 \fI\-L /path/to/list\fR [\fI\-a john@doe.org\fR | \fI\-m str\fR]
-[\fI\-c\fR | \fI\-C\fR] [\fI\-d\fR | \fI\-n\fR] [\fI\-f\fR] [\fI\-h\fR] \fR[\fI\-r\fR | \fI\-R\fR] [\fI\-s\fR] [\fI\-U\fR] [\fI\-V\fR]
+[\fI\-c\fR] [\fI\-C\fR] [\fI\-d\fR | \fI\-n\fR] [\fI\-f\fR] [\fI\-h\fR] [\fI\-q\fR] \fR[\fI\-r\fR | \fI\-R\fR] [\fI\-s\fR] [\fI\-U\fR] [\fI\-V\fR]
 .HP
 \fB\-a\fR: Email address to subscribe
 .HP
-\fB\-c\fR: Send welcome mail
+\fB\-c\fR: Send welcome mail (unless requesting confirmation)
 .HP
-\fB\-C\fR: Request mail confirmation
+\fB\-C\fR: Request mail confirmation (unless switching versions)
 .HP
 \fB\-d\fR: Subscribe to digest version of the list
 .HP
@@ -24,6 +24,8 @@
 .HP
 \fB\-n\fR: Subscribe to nomail version of the list
 .HP
+\fB\-q\fR: Be quiet (don't notify owner about the subscription)
+.HP
 \fB\-r\fR: Behave as if request arrived via email (internal use)
 .HP
 \fB\-R\fR: Behave as if confirmation arrived via email (internal use)
@@ -38,23 +40,35 @@
 write the email address in a file with the name of the beginning letter of the
 email address getting subscribed in the <listdir>/subscribers.d/ directory.
 
-Unless the \fB\-U\fR switch is used it will switch its user id to the user id
-owning the list directory. This is done to make sure that new files created are
-having correct permissions.
+The digest version of the list is a list version where people receive postings
+to the list periodically (e.g. once a day) or when a large number of posts have
+accumulated. Digest subscribers are in the <listdir>/digesters.d/ directory.
 
 The nomail version of the list is a list version where people are subscribed
 like usual, but they won't receive any postings to the list. This is useful for
 people who read the mailinglist through a news gateway, but want to be able to
-post to the list.
+post to the list. Nomail subscribers are in the <listdir>/nomailsubs.d/
+directory.
 
-Normally a mail is sent to the subscriber if the address is already subscribed
-to the list. If the \fB\-s\fR switch is used such a mail will not be sent.
+Unless the \fB\-U\fR switch is used it will switch its user id to the user id
+owning the list directory. This is done to make sure that new files created are
+having correct permissions.
+
+If the given address is already subscribed to the list, but to a different
+version, the subscription is switched to that version, and confirmation and
+moderation are bypassed. If the address is already subscribed to the version
+requested, a mail is sent to the subscriber, unless the \fB\-s\fR switch is
+used.
 
 Subscription may be moderated (if <listdir>/control/submod exists) unless the
-\fB\-f\fR switch is given.
+\fB\-f\fR switch is given. When a subscription is permitted by a gatekeeper,
+welcome messages are sent to the subscriber as usual, regardless of options
+given now.
 
-To ensure a silent subscription, use \fB\-f\fR, but neither \fB\-c\fR
-nor \fB\-C\fR.
+To ensure subscription is silent from the point of view of the subscriber, use
+\fB\-f\fR, but neither \fB\-c\fR nor \fB\-C\fR. To inhibit notification of the
+owner, use \fB\-q\fR. Use of \fB\-s\fR is recommended to ensure you don't spam
+already-subscribed addresses by accident.
 .SH "SEE ALSO"
 mlmmj-unsub(1), setuid(2)
 .SH AUTHORS
--- a/man/mlmmj-unsub.1	Tue Jan 24 14:20:58 2012 +1100
+++ b/man/mlmmj-unsub.1	Tue Jan 24 03:02:00 2012 +1100
@@ -4,7 +4,7 @@
 .SH SYNOPSIS
 .B mlmmj-unsub
 \fI\-L /path/to/list \-a john@doe.org\fR [\fI\-b\fR] [\fI\-c\fR | \fI\-C\fR]
-[\fI\-d\fR | \fI\-n\fR] [\fI\-h\fR] [\fI\-r\fR | \fI\-R\fR] [\fI\-s\fR] [\fI\-U\fR] [\fI\-V\fR]
+[\fI\-d\fR | \fI\-n\fR | \fI\-N\fR] [\fI\-h\fR] [\fI\-q\fR] [\fI\-r\fR | \fI\-R\fR] [\fI\-s\fR] [\fI\-U\fR] [\fI\-V\fR]
 .HP
 \fB\-a\fR: Email address to unsubscribe
 .HP
@@ -22,6 +22,10 @@
 .HP
 \fB\-n\fR: Unsubscribe from the nomail version of the list
 .HP
+\fB\-N\fR: Unsubscribe from the normal version of the list
+.HP
+\fB\-q\fR: Be quiet (don't notify owner about the unsubscribe)
+.HP
 \fB\-r\fR: Behave as if request arrived via email (internal use)
 .HP
 \fB\-R\fR: Behave as if confirmation arrived via email (internal use)
@@ -34,7 +38,9 @@
 .SH DESCRIPTION
 This utility is used to unsubscribe people from the specified mailinglist. It
 will remove the specified email address from every file in the
-<listdir>/subscribers.d/ directory.
+<listdir>/subscribers.d/, <listdir>/digesters.d/ and <listdir>/nomailsubs.d/
+directories (or if the \-d, \-n or \-N switch is given, only the one relevant
+directory).
 
 Unless the \fB\-U\fR switch is used it will switch its user id to the user id
 owning the list directory. This is done to make sure that new files created are
@@ -44,8 +50,11 @@
 subscribed to the list. If the \fB\-s\fR switch is used such a mail will not be
 sent.
 
-When neither \fB\-c\fR nor \fB\-C\fR are specified, unsubscription silently
-happens.
+When neither \fB\-c\fR nor \fB\-C\fR is specified, unsubscription happens
+silently from the point of view of the subscriber. When \fB\-q\fR is specified,
+unsubscription happens silently from the point of view of the list owner. Use
+of \fB\-s\fR is recommended to ensure you don't spam unsubscribed addresses by
+accident.
 .SH "SEE ALSO"
 mlmmj-sub(1)
 .SH AUTHORS
--- a/src/listcontrol.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/listcontrol.c	Tue Jan 24 03:02:00 2012 +1100
@@ -118,7 +118,6 @@
 	char *omitfilename;
 	char *omit = NULL;
 	char *c, *archivefilename, *sendfilename;
-	const char *subswitch;
 	struct stat stbuf;
 	int closedlist, nosubconfirm, tmpfd, noget, i, closedlistsub,
 	    subonlyget = 0;
@@ -136,10 +135,6 @@
 	closedlistsub = statctrl(listdir, "closedlistsub");
 
 	nosubconfirm = statctrl(listdir, "nosubconfirm");
-	if(nosubconfirm)
-		subswitch = "-c";
-	else
-		subswitch = "-C";
 	
 #if 0
 	log_error(LOG_ARGS, "controlstr = [%s]\n", controlstr);
@@ -244,7 +239,9 @@
 				"-L", listdir,
 				"-a", fromemails->emaillist[0],
 				"-d",
-				"-r", subswitch, (char *)NULL);
+				"-r", "-c",
+				(nosubconfirm ? (char *)NULL : "-C"),
+				(char *)NULL);
 		log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjsub);
 		exit(EXIT_FAILURE);
@@ -291,7 +288,9 @@
 				"-L", listdir,
 				"-a", fromemails->emaillist[0],
 				"-n",
-				"-r", subswitch, (char *)NULL);
+				"-r", "-c",
+				(nosubconfirm ? (char *)NULL : "-C"),
+				(char *)NULL);
 		log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjsub);
 		exit(EXIT_FAILURE);
@@ -319,7 +318,9 @@
 		execlp(mlmmjsub, mlmmjsub,
 				"-L", listdir,
 				"-a", fromemails->emaillist[0],
-				"-r", subswitch, (char *)NULL);
+				"-r", "-c",
+				(nosubconfirm ? (char *)NULL : "-C"),
+				(char *)NULL);
 		log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjsub);
 		exit(EXIT_FAILURE);
@@ -409,64 +410,10 @@
 		exit(EXIT_FAILURE);
 		break;
 
-	/* listname+unsubscribe-digest@domain.tld */
+	/* DEPRECATED: listname+unsubscribe-digest@domain.tld */
 	case CTRL_UNSUBSCRIBE_DIGEST:
-		if (closedlist) {
-			errno = 0;
-			log_error(LOG_ARGS, "An unsubscribe-digest 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, "An unsubscribe-digest request was"
-				" sent with an invalid From: header."
-				" Ignoring mail");
-			return -1;
-		}
-		log_oper(listdir, OPLOGFNAME, "mlmmj-unsub: %s requests"
-					" unsubscribe from digest",
-					fromemails->emaillist[0]);
-		execlp(mlmmjunsub, mlmmjunsub,
-				"-L", listdir,
-				"-a", fromemails->emaillist[0],
-				"-d",
-				"-r", subswitch, (char *)NULL);
-		log_error(LOG_ARGS, "execlp() of '%s' failed",
-				mlmmjunsub);
-		exit(EXIT_FAILURE);
-		break;
-
-	/* listname+unsubscribe-nomail@domain.tld */
+	/* DEPRECATED: listname+unsubscribe-nomail@domain.tld */
 	case CTRL_UNSUBSCRIBE_NOMAIL:
-		if (closedlist) {
-			errno = 0;
-			log_error(LOG_ARGS, "An unsubscribe-nomail 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, "An unsubscribe-nomail request was"
-				" sent with an invalid From: header."
-				" Ignoring mail");
-			return -1;
-		}
-		log_oper(listdir, OPLOGFNAME, "mlmmj-unsub: %s requests"
-					" unsubscribe from nomail",
-					fromemails->emaillist[0]);
-		execlp(mlmmjunsub, mlmmjunsub,
-				"-L", listdir,
-				"-a", fromemails->emaillist[0],
-				"-n",
-				"-r", subswitch, (char *)NULL);
-		log_error(LOG_ARGS, "execlp() of '%s' failed",
-				mlmmjunsub);
-		exit(EXIT_FAILURE);
-		break;
-
 	/* listname+unsubscribe@domain.tld */
 	case CTRL_UNSUBSCRIBE:
 		if (closedlist) {
@@ -484,12 +431,13 @@
 			return -1;
 		}
 		log_oper(listdir, OPLOGFNAME, "mlmmj-unsub: %s requests"
-					" unsubscribe from regular list",
+					" unsubscribe",
 					fromemails->emaillist[0]);
 		execlp(mlmmjunsub, mlmmjunsub,
 				"-L", listdir,
 				"-a", fromemails->emaillist[0],
-				"-r", subswitch, (char *)NULL);
+				"-r", (nosubconfirm ? "-c" : "-C"),
+				(char *)NULL);
 		log_error(LOG_ARGS, "execlp() of '%s' failed",
 				mlmmjunsub);
 		exit(EXIT_FAILURE);
@@ -815,7 +763,8 @@
 		}
 		subonlyget = statctrl(listdir, "subonlyget");
 		if(subonlyget) {
-			if(is_subbed(listdir, fromemails->emaillist[0]) != 0) {
+			if(is_subbed(listdir, fromemails->emaillist[0]) ==
+					SUB_NONE) {
 				errno = 0;
 				log_error(LOG_ARGS, "A get request was sent"
 					" from a non-subscribed address to a"
--- a/src/mlmmj-bounce.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/mlmmj-bounce.c	Tue Jan 24 03:02:00 2012 +1100
@@ -342,7 +342,7 @@
 	*a = '@';
 
 	/* make sure it's a subscribed address */
-	if(is_subbed(listdir, address)) {
+	if(is_subbed(listdir, address) == SUB_NONE) {
 		log_error(LOG_ARGS, "%s is bouncing but not subscribed?",
 				address);
 		if(mailname)
--- a/src/mlmmj-process.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/mlmmj-process.c	Tue Jan 24 03:02:00 2012 +1100
@@ -912,7 +912,7 @@
 			myfree(donemailname);
 			exit(EXIT_SUCCESS);
 		}
-		if(is_subbed(listdir, posteraddr) != 0) {
+		if(is_subbed(listdir, posteraddr) == SUB_NONE) {
 			modnonsubposts = statctrl(listdir,
 					"modnonsubposts");
 			if(modnonsubposts) {
--- a/src/mlmmj-sub.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/mlmmj-sub.c	Tue Jan 24 03:02:00 2012 +1100
@@ -55,7 +55,8 @@
 	"digest",
 	"nomail",
 	"file",
-	"all"
+	"all",
+	"none"
 };
 
 char * subreason_strs[] = {
@@ -63,7 +64,8 @@
 	"confirm",
 	"permit",
 	"admin",
-	"bouncing"
+	"bouncing",
+	"switch"
 };
 
 static void moderate_sub(const char *listdir, const char *listaddr,
@@ -494,28 +496,28 @@
 static void print_help(const char *prg)
 {
 	printf("Usage: %s -L /path/to/list {-a john@doe.org | -m str}\n"
-	       "       [-c | -C] [-f] [-h] [-L] [-d | -n] [-r | -R] [-s] [-U] [-V]\n"
+	       "       [-c] [-C] [-f] [-h] [-L] [-d | -n] [-q] [-r | -R] [-s] [-U] [-V]\n"
 	       " -a: Email address to subscribe \n"
-	       " -c: Send welcome mail\n"
-	       " -C: Request mail confirmation\n"
+	       " -c: Send welcome mail (unless requesting confirmation)\n"
+	       " -C: Request mail confirmation (unless switching versions)\n"
 	       " -d: Subscribe to digest of list\n"
 	       " -f: Force subscription (do not moderate)\n"
 	       " -h: This help\n"
 	       " -L: Full path to list directory\n"
 	       " -m: moderation string\n"
 	       " -n: Subscribe to no mail version of list\n", prg);
-	printf(" -r: Behave as if request arrived via email (internal use)\n"
+	printf(" -q: Be quiet (don't notify owner about the subscription)\n"
+	       " -r: Behave as if request arrived via email (internal use)\n"
 	       " -R: Behave as if confirmation arrived via email (internal use)\n"
 	       " -s: Don't send a mail to subscriber if already subscribed\n"
 	       " -U: Don't switch to the user id of the listdir owner\n"
 	       " -V: Print version\n"
-	       "When no options are specified, subscription may be "
-	       "moderated;\nto ensure a silent subscription, use -f\n");
+	       "To ensure a silent subscription, use -f -q -s\n");
 	exit(EXIT_SUCCESS);
 }
 
 void generate_subscribed(const char *listdir, const char *subaddr,
-		const char *mlmmjsend)
+		const char *mlmmjsend, enum subtype typesub)
 {
 	text *txt;
 	char *queuefilename, *fromaddr, *listname, *listfqdn, *listaddr;
@@ -529,7 +531,8 @@
 	myfree(listdelim);
 
 	txt = open_text(listdir,
-			"deny", "sub", "subbed", NULL, "sub-subscribed");
+			"deny", "sub", "subbed", subtype_strs[typesub],
+			"sub-subscribed");
 	MY_ASSERT(txt);
 	register_unformatted(txt, "subaddr", subaddr);
 	queuefilename = prepstdreply(txt, listdir,
@@ -555,16 +558,18 @@
 int main(int argc, char **argv)
 {
 	char *listaddr, *listdelim, *listdir = NULL, *address = NULL;
-	char *subfilename = NULL, *mlmmjsend, *bindir, chstr[2], *subdir;
+	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, subbed;
-	int force = 0;
+	int groupwritable = 0, sublock, sublockfd, nogensubscribed = 0;
+	int force = 0, quiet = 0;
+	enum subtype subbed;
 	size_t len;
 	struct stat st;
-	pid_t pid, childpid;
+	pid_t pid, childpid = 0;
 	uid_t uid;
 	enum subtype typesub = SUB_NORMAL;
 	enum subreason reasonsub = SUB_ADMIN;
@@ -575,9 +580,10 @@
 
 	bindir = mydirname(argv[0]);
 	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
+	mlmmjunsub = concatstr(2, bindir, "/mlmmj-unsub");
 	myfree(bindir);
 
-	while ((opt = getopt(argc, argv, "hcCdfm:nsVUL:a:rR")) != -1) {
+	while ((opt = getopt(argc, argv, "hcCdfm:nsVUL:a:qrR")) != -1) {
 		switch(opt) {
 		case 'a':
 			address = optarg;
@@ -606,6 +612,9 @@
 		case 'n':
 			nomail = 1;
 			break;
+		case 'q':
+			quiet = 1;
+			break;
 		case 'r':
 			reasonsub = SUB_REQUEST;
 			break;
@@ -648,7 +657,7 @@
 	}
 
 	if(digest && nomail) {
-		fprintf(stderr, "Specify either -d or -n, not both\n");
+		fprintf(stderr, "Specify at most one of -d and -n\n");
 		fprintf(stderr, "%s -h for help\n", argv[0]);
 		exit(EXIT_FAILURE);
 	}
@@ -658,12 +667,6 @@
 	if(nomail)
 		typesub = SUB_NOMAIL;
 
-	if(confirmsub && subconfirm) {
-		fprintf(stderr, "Cannot specify both -C and -c\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
 	if(reasonsub == SUB_CONFIRM && subconfirm) {
 		fprintf(stderr, "Cannot specify both -C and -R\n");
 		fprintf(stderr, "%s -h for help\n", argv[0]);
@@ -760,11 +763,45 @@
 		myfree(sublockname);
 		exit(EXIT_FAILURE);
 	}
-	subbed = is_subbed_in(subddirname, address);
+	subbed = is_subbed(listdir, address);
 	listdelim = getlistdelim(listdir);
 	
-	if(subbed) {
-		if(subconfirm) {
+	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;
+		childpid = fork();
+		if(childpid < 0)
+				log_error(LOG_ARGS, "Could not fork; "
+				"not unsubscribed from current version");
+		if (childpid == 0) {
+			execlp(mlmmjunsub, mlmmjunsub,
+					"-L", listdir, "-q",
+					"-a", address,
+					(char *)NULL);
+			log_error(LOG_ARGS, "execlp() of '%s' failed",
+					mlmmjunsub);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	if(childpid > 0) {
+		do /* Parent waits for the child */
+			pid = waitpid(childpid, &status, 0);
+		while(pid == -1 && errno == EINTR);
+	}
+
+	if(subbed == SUB_NONE && subconfirm) {
 			close(subfilefd);
 			close(sublockfd);
 			unlink(sublockname);
@@ -773,7 +810,8 @@
 					    address, mlmmjsend, typesub, reasonsub);
 		} else {
 			if(modstr == NULL)
-				submod = !force && statctrl(listdir, "submod");
+				submod = subbed == SUB_NONE && !force &&
+				statctrl(listdir, "submod");
 			if(submod) {
 				close(subfilefd);
 				close(sublockfd);
@@ -791,18 +829,6 @@
 			close(sublockfd);
 			unlink(sublockname);
 		}
-	} else {
-		close(subfilefd);
-		myfree(subfilename);
-		close(sublockfd);
-		unlink(sublockname);
-		myfree(sublockname);
-
-		if(!nogensubscribed)
-			generate_subscribed(listdir, address, mlmmjsend);
-		
-		return EXIT_SUCCESS;
-	}
 
 	close(sublockfd);
 	unlink(sublockname);
@@ -829,7 +855,8 @@
 					mlmmjsend, typesub, reasonsub);
 	}
 
-	notifysub = statctrl(listdir, "notifysub");
+	notifysub = !quiet && reasonsub != SUB_SWITCH &&
+			statctrl(listdir, "notifysub");
 
 	/* Notify list owner about subscription */
 	if (notifysub)
--- a/src/mlmmj-unsub.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/mlmmj-unsub.c	Tue Jan 24 03:02:00 2012 +1100
@@ -293,7 +293,7 @@
 static void print_help(const char *prg)
 {
 	printf("Usage: %s -L /path/to/list -a john@doe.org\n"
-	       "       [-b] [-c | -C] [-h] [-L] [-d | -n] [-r | -R] [-s] [-V]\n"
+	       "       [-b] [-c | -C] [-h] [-L] [-d | -n | -N] [-q] [-r | -R] [-s] [-V]\n"
 	       " -a: Email address to unsubscribe \n"
 	       " -b: Behave as if unsubscription is due to bouncing (internal use)\n"
 	       " -c: Send goodbye mail\n"
@@ -301,19 +301,20 @@
 	       " -d: Unsubscribe from digest of list\n"
 	       " -h: This help\n"
 	       " -L: Full path to list directory\n"
-	       " -n: Unsubscribe from no mail version of list\n", prg);
-	printf(" -r: Behave as if request arrived via email (internal use)\n"
+	       " -n: Unsubscribe from no mail version of list\n" 
+	       " -N: Unsubscribe from normal version of list\n", prg);
+	printf(" -q: Be quiet (don't notify owner about the subscription)\n"
+	       " -r: Behave as if request arrived via email (internal use)\n"
 	       " -R: Behave as if confirmation arrived via email (internal use)\n"
 	       " -s: Don't send a mail to the address if not subscribed\n"
 	       " -U: Don't switch to the user id of the listdir owner\n"
 	       " -V: Print version\n"
-	       "When no options are specified, unsubscription silently "
-	       "happens\n");
+	       "To ensure a silent unsubscription, use -q -s\n");
 	exit(EXIT_SUCCESS);
 }
 
-void generate_notsubscribed(const char *listdir, const char *subaddr,
-		const char *mlmmjsend)
+static void generate_notsubscribed(const char *listdir, const char *subaddr,
+		const char *mlmmjsend, enum subtype typesub)
 {
 	text *txt;
 	char *queuefilename, *fromaddr, *listname, *listfqdn, *listaddr;
@@ -327,7 +328,7 @@
 	myfree(listdelim);
 
 	txt = open_text(listdir,
-			"deny", "unsub", "unsubbed", NULL,
+			"deny", "unsub", "unsubbed", subtype_strs[typesub],
 			"unsub-notsubscribed");
 	MY_ASSERT(txt);
 	register_unformatted(txt, "subaddr", subaddr);
@@ -351,135 +352,20 @@
 	exit(EXIT_FAILURE);
 }
 
-
-int main(int argc, char **argv)
-{
-	int subread, subwrite, rlock, wlock, opt, unsubres, status, nomail = 0;
-	int confirmunsub = 0, unsubconfirm = 0, notifysub = 0, digest = 0;
-	int changeuid = 1, groupwritable = 0, sublock, sublockfd;
-	int nogennotsubscribed = 0, i = 0;
-	char *listaddr, *listdelim, *listdir = NULL, *address = NULL;
-	char *subreadname = NULL, *subwritename, *mlmmjsend, *bindir, *subdir;
-	char *subddirname, *sublockname, *lowcaseaddr;
-	off_t suboff;
+static void unsubscribe_type(char *listdir, char *listaddr, char *listdelim,
+		char *address, char *mlmmjsend, int confirmunsub,
+		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;
+	struct stat st;
 	DIR *subddir;
 	struct dirent *dp;
+	off_t suboff;
 	pid_t pid, childpid;
-	enum subtype typesub = SUB_NORMAL;
-	enum subreason reasonsub = SUB_ADMIN;
-	uid_t uid;
-	struct stat st;
-
-	CHECKFULLPATH(argv[0]);
-	
-	log_set_name(argv[0]);
-
-	bindir = mydirname(argv[0]);
-	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
-	myfree(bindir);
-
-	while ((opt = getopt(argc, argv, "hcCdnVUL:a:sbrR")) != -1) {
-		switch(opt) {
-		case 'L':
-			listdir = optarg;
-			break;
-		case 'n':
-			nomail = 1;
-			break;
-		case 'a':
-			address = optarg;
-			break;
-		case 'b':
-			reasonsub = SUB_BOUNCING;
-			break;
-		case 'c':
-			confirmunsub = 1;
-			break;
-		case 'C':
-			unsubconfirm = 1;
-			break;
-		case 'd':
-			digest = 1;
-			break;
-		case 'h':
-			print_help(argv[0]);
-			break;
-		case 'r':
-			reasonsub = SUB_REQUEST;
-			break;
-		case 'R':
-			reasonsub = SUB_CONFIRM;
-			break;
-		case 's':
-			nogennotsubscribed = 1;
-			break;
-		case 'U':
-			changeuid = 0;
-			break;
-		case 'V':
-			print_version(argv[0]);
-			exit(0);
-		}
-	}
-	if(listdir == 0 || address == 0) {
-		fprintf(stderr, "You have to specify -L and -a\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	if(digest && nomail) {
-		fprintf(stderr, "Specify either -d or -n, not both\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	if(digest)
-		typesub = SUB_DIGEST;
-	if(nomail)
-		typesub = SUB_NOMAIL;
-
-	if(confirmunsub && unsubconfirm) {
-		fprintf(stderr, "Cannot specify both -C and -c\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	if(reasonsub == SUB_CONFIRM && unsubconfirm) {
-		fprintf(stderr, "Cannot specify both -C and -R\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	if(reasonsub == SUB_BOUNCING && unsubconfirm) {
-		fprintf(stderr, "Cannot specify both -C and -b\n");
-		fprintf(stderr, "%s -h for help\n", argv[0]);
-		exit(EXIT_FAILURE);
-	}
-
-	/* Make the address lowercase */
-	lowcaseaddr = mystrdup(address);
-	i = 0;
-	while(lowcaseaddr[i]) {
-		lowcaseaddr[i] = tolower(lowcaseaddr[i]);
-		i++;
-	}
-	address = lowcaseaddr;
-
-	/* get the list address */
-	listaddr = getlistaddr(listdir);
-
-	if(changeuid) {
-		uid = getuid();
-		if(!uid && stat(listdir, &st) == 0) {
-			printf("Changing to uid %d, owner of %s.\n",
-					(int)st.st_uid, listdir);
-			if(setuid(st.st_uid) < 0) {
-				perror("setuid");
-				fprintf(stderr, "Continuing as uid %d\n",
-						(int)uid);
-			}
-		}
-	}
 
 	switch(typesub) {
 		default:
@@ -502,29 +388,10 @@
 		}
 	}
 
-	if(is_subbed_in(subddirname, address)) {
-		/* Address is not subscribed */
-		myfree(subddirname);
-		myfree(listaddr);
-
-		if(!nogennotsubscribed) {
-			generate_notsubscribed(listdir, address, mlmmjsend);
-		}
-
-		exit(EXIT_SUCCESS);
-	}
-
-	listdelim = getlistdelim(listdir);
-	if(unsubconfirm)
-		generate_unsubconfirm(listdir, listaddr, listdelim, address,
-				mlmmjsend, typesub, reasonsub);
-
 	if((subddir = opendir(subddirname)) == NULL) {
 		log_error(LOG_ARGS, "Could not opendir(%s)",
 				    subddirname);
 		myfree(subddirname);
-		myfree(listaddr);
-		myfree(listdelim);
 		exit(EXIT_FAILURE);
 	}
 
@@ -672,8 +539,194 @@
         }
 
 	closedir(subddir);
+}
 
-        notifysub = statctrl(listdir, "notifysub");
+
+int main(int argc, char **argv)
+{
+	int opt;
+	int normal = 0, digest = 0, nomail = 0, subbed;
+	int confirmunsub = 0, unsubconfirm = 0, notifysub = 0;
+	int changeuid = 1, quiet = 0;
+	int nogennotsubscribed = 0, i = 0;
+	char *listaddr, *listdelim, *listdir = NULL, *address = NULL;
+	char *mlmmjsend, *bindir, *subdir, *subddirname;
+	char *lowcaseaddr;
+	enum subtype typesub = SUB_ALL;
+	enum subreason reasonsub = SUB_ADMIN;
+	uid_t uid;
+	struct stat st;
+
+	CHECKFULLPATH(argv[0]);
+	
+	log_set_name(argv[0]);
+
+	bindir = mydirname(argv[0]);
+	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
+	myfree(bindir);
+
+	while ((opt = getopt(argc, argv, "hcCdenNVUL:a:sbqrR")) != -1) {
+		switch(opt) {
+		case 'L':
+			listdir = optarg;
+			break;
+		case 'a':
+			address = optarg;
+			break;
+		case 'b':
+			reasonsub = SUB_BOUNCING;
+			break;
+		case 'c':
+			confirmunsub = 1;
+			break;
+		case 'C':
+			unsubconfirm = 1;
+			break;
+		case 'd':
+			digest = 1;
+			break;
+		case 'h':
+			print_help(argv[0]);
+			break;
+		case 'n':
+			nomail = 1;
+			break;
+		case 'N':
+			normal = 1;
+			break;
+		case 'q':
+			quiet = 1;
+			break;
+		case 'r':
+			reasonsub = SUB_REQUEST;
+			break;
+		case 'R':
+			reasonsub = SUB_CONFIRM;
+			break;
+		case 's':
+			nogennotsubscribed = 1;
+			break;
+		case 'U':
+			changeuid = 0;
+			break;
+		case 'V':
+			print_version(argv[0]);
+			exit(0);
+		}
+	}
+	if(listdir == 0 || address == 0) {
+		fprintf(stderr, "You have to specify -L and -a\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if(digest + nomail + normal > 1) {
+		fprintf(stderr, "Specify at most one of -d, -n 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(normal)
+		typesub = SUB_NORMAL;
+
+	if(confirmunsub && unsubconfirm) {
+		fprintf(stderr, "Cannot specify both -C and -c\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if(reasonsub == SUB_CONFIRM && unsubconfirm) {
+		fprintf(stderr, "Cannot specify both -C and -R\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if(reasonsub == SUB_BOUNCING && unsubconfirm) {
+		fprintf(stderr, "Cannot specify both -C and -b\n");
+		fprintf(stderr, "%s -h for help\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	/* Make the address lowercase */
+	lowcaseaddr = mystrdup(address);
+	i = 0;
+	while(lowcaseaddr[i]) {
+		lowcaseaddr[i] = tolower(lowcaseaddr[i]);
+		i++;
+	}
+	address = lowcaseaddr;
+
+	/* get the list address */
+	listaddr = getlistaddr(listdir);
+	listdelim = getlistdelim(listdir);
+
+	if(changeuid) {
+		uid = getuid();
+		if(!uid && stat(listdir, &st) == 0) {
+			printf("Changing to uid %d, owner of %s.\n",
+					(int)st.st_uid, listdir);
+			if(setuid(st.st_uid) < 0) {
+				perror("setuid");
+				fprintf(stderr, "Continuing as uid %d\n",
+						(int)uid);
+			}
+		}
+	}
+
+	if (typesub == SUB_ALL) {
+		subbed = is_subbed(listdir, address) != SUB_NONE;
+	} else {
+		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);
+		subbed = is_subbed_in(subddirname, address);
+		myfree(subddirname);
+	}
+
+	if(!subbed) {
+		/* Address is not subscribed */
+		myfree(listaddr);
+		myfree(listdelim);
+
+		if(!nogennotsubscribed) {
+			generate_notsubscribed(listdir, address, mlmmjsend,
+					typesub);
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+
+	if(unsubconfirm)
+		generate_unsubconfirm(listdir, listaddr, listdelim, address,
+				mlmmjsend, typesub, reasonsub);
+
+	if (typesub == SUB_ALL) {
+		unsubscribe_type(listdir, listaddr, listdelim, address,
+				mlmmjsend, confirmunsub, SUB_NORMAL, reasonsub);
+		unsubscribe_type(listdir, listaddr, listdelim, address,
+				mlmmjsend, confirmunsub, SUB_DIGEST, reasonsub);
+		unsubscribe_type(listdir, listaddr, listdelim, address,
+				mlmmjsend, confirmunsub, SUB_NOMAIL, reasonsub);
+	} else {
+		unsubscribe_type(listdir, listaddr, listdelim, address,
+				mlmmjsend, confirmunsub, typesub, reasonsub);
+	}
+
+        notifysub = !quiet && statctrl(listdir, "notifysub");
 
         /* Notify list owner about subscription */
         if (notifysub)
--- a/src/subscriberfuncs.c	Tue Jan 24 14:20:58 2012 +1100
+++ b/src/subscriberfuncs.c	Tue Jan 24 03:02:00 2012 +1100
@@ -93,7 +93,7 @@
 
 int is_subbed_in(const char *subddirname, const char *address)
 {
-	int retval = 1, subread;
+	int retval = 0, subread;
 	char *subreadname;
 	off_t suboff;
 	DIR *subddir;
@@ -124,7 +124,7 @@
 		if(suboff == -1) {
 			continue;
 		} else {
-			retval = 0;
+			retval = 1;
 			break;
 		}
 	}
@@ -133,7 +133,7 @@
 	return retval;
 }
 
-int is_subbed(const char *listdir, const char *address)
+enum subtype is_subbed(const char *listdir, const char *address)
 {
 	int retval;
 	char *subddirname;
@@ -141,20 +141,17 @@
 	subddirname = concatstr(2, listdir, "/subscribers.d/");
 	retval = is_subbed_in(subddirname, address);
 	myfree(subddirname);
-	if (retval == 0)
-		return 0;
+	if (retval) return SUB_NORMAL;
 
 	subddirname = concatstr(2, listdir, "/digesters.d/");
 	retval = is_subbed_in(subddirname, address);
 	myfree(subddirname);
-	if (retval == 0)
-		return 0;
+	if (retval) return SUB_DIGEST;
 
 	subddirname = concatstr(2, listdir, "/nomailsubs.d/");
 	retval = is_subbed_in(subddirname, address);
 	myfree(subddirname);
-	if (retval == 0)
-		return 0;
+	if (retval) return SUB_NOMAIL;
 
-	return 1;
+	return SUB_NONE;
 }