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,
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 },
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) {
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);
}
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"
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 (;;) {
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);
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",
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",
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, "@",
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;
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;
exit(EXIT_FAILURE);
}
- if(modstr) {
- getaddrandtype(listdir, modstr, &address, &typesub);
- reasonsub = SUB_PERMIT;
- }
-
- if(strchr(address, '@') == NULL) {
- log_error(LOG_ARGS, "No '@' sign in '%s', not subscribing",
- address);
- exit(EXIT_SUCCESS);
- }
-
- if(digest && nomail) {
- fprintf(stderr, "Specify at most one of -d and -n\n");
+ 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);
}
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");
exit(EXIT_FAILURE);
}
+ if(modstr) {
+ getaddrandtype(listdir, modstr, &address, &typesub);
+ reasonsub = SUB_PERMIT;
+ }
+
+ if(strchr(address, '@') == NULL) {
+ log_error(LOG_ARGS, "No '@' sign in '%s', not subscribing",
+ address);
+ exit(EXIT_SUCCESS);
+ }
+
/* Make the address lowercase */
lowcaseaddr = mystrdup(address);
i = 0;
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) {
}
}
- 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);
- }
+ subbed = is_subbed(listdir, address, 1);
- 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);
-
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)
+ /* 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) {
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) {
+ if (subbed == SUB_BOTH) {
+ if (typesub == SUB_NORMAL) flag = "-d";
+ if (typesub == SUB_DIGEST) flag = "-N";
+ }
+ execlp(mlmmjunsub, mlmmjunsub,
+ "-L", listdir, "-q",
+ "-a", address, flag,
+ (char *)NULL);
+ log_error(LOG_ARGS, "execlp() of '%s' failed",
+ mlmmjunsub);
+ exit(EXIT_FAILURE);
+ }
}
}
while(pid == -1 && errno == EINTR);
}
- if(subbed == SUB_NONE && subconfirm) {
- close(subfilefd);
- close(sublockfd);
- unlink(sublockname);
- myfree(sublockname);
- 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);
- moderate_sub(listdir, listaddr, listdelim,
+ listdelim = getlistdelim(listdir);
+
+ if(subbed == SUB_NONE && subconfirm)
+ generate_subconfirm(listdir, listaddr, listdelim,
+ address, mlmmjsend, typesub, reasonsub);
+
+ 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);
}
- close(sublockfd);
- unlink(sublockname);
- myfree(sublockname);
+ if (typesub == SUB_BOTH) {
+ if (subbed != SUB_NORMAL) {
+ subscribe_type(listdir, listaddr, listdelim, address,
+ mlmmjsend, SUB_NORMAL, reasonsub);
+ }
+ 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();
}
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:
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;
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;
enum subreason reasonsub = SUB_ADMIN;
uid_t uid;
struct stat st;
+ pid_t pid, childpid;
CHECKFULLPATH(argv[0]);
}
if (typesub == SUB_ALL) {
- subbed = is_subbed(listdir, address) != SUB_NONE;
+ subbed = is_subbed(listdir, address, 0) != SUB_NONE;
} else {
switch(typesub) {
default:
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");