From: Baptiste Daroussin Date: Fri, 28 Oct 2022 12:48:55 +0000 (+0200) Subject: Add function to unsubscribe people X-Git-Tag: RELEASE_1_4_0a1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=44dfbc1d53bfaa710d5057fcf767e8470f966a9b;p=thirdparty%2Fmlmmj.git Add function to unsubscribe people Add the testsuite that goes with it. --- diff --git a/include/mlmmj.h b/include/mlmmj.h index 879387c7..9c4b93ee 100644 --- a/include/mlmmj.h +++ b/include/mlmmj.h @@ -74,7 +74,7 @@ struct mailhdr { /* Has to go here, since it's used in many places */ enum subtype { - SUB_NORMAL, + SUB_NORMAL = 0, SUB_DIGEST, SUB_NOMAIL, SUB_FILE, /* For single files (moderator, owner etc.) */ @@ -100,6 +100,8 @@ void print_version(const char *prg); bool parse_lastdigest(char *line, long *lastindex, time_t *lasttime, long *lastissue, const char **errstr); time_t extract_bouncetime(char *line, const char **errstr); +int open_subscriber_directory(int listfd, enum subtype typesub, const char **dir); +bool do_unsubscribe(int listfd, const char *address, enum subtype typesub); #define MY_ASSERT(expression) if (!(expression)) { \ errno = 0; \ diff --git a/src/mlmmj.c b/src/mlmmj.c index 724c94ae..4c0d55f6 100644 --- a/src/mlmmj.c +++ b/src/mlmmj.c @@ -112,3 +112,173 @@ extract_bouncetime(char *line, const char **errstr) return (strtotimet(walk, errstr)); } + +static const char *subtype_dirs[] = { + "subscribers.d", + "digesters.d", + "nomailsubs.d", + NULL, + NULL, + NULL, + NULL, +}; + +int +open_subscriber_directory(int listfd, enum subtype typesub, const char **subdir) +{ + const char *dir = subtype_dirs[typesub]; + + if (subdir != NULL) + *subdir = dir; + if (dir == NULL) + return (-1); + return (openat(listfd, dir, O_DIRECTORY|O_CLOEXEC)); +} + +bool +do_unsubscribe(int listfd, const char *address, enum subtype typesub) +{ + struct dirent *dp; + DIR *dir; + bool ret = true; + struct stat st; + int fd; + int groupwritable = 0; + const char *subdir; + size_t alen, len; + + switch (typesub) { + case SUB_ALL: + if (!do_unsubscribe(listfd, address, SUB_NORMAL)) + ret = false; + if (!do_unsubscribe(listfd, address, SUB_DIGEST)) + ret = false; + if (!do_unsubscribe(listfd, address, SUB_NOMAIL)) + ret = false; + return (ret); + case SUB_NORMAL: + case SUB_DIGEST: + case SUB_NOMAIL: + break; + default: + return (false); + } + + fd = open_subscriber_directory(listfd, typesub, &subdir); + if (fd == -1 || (dir = fdopendir(fd)) == NULL) { + log_err("Could not opendir(%s)", subdir); + return (false); + } + + if (fstat(fd, &st) == 0) { + if (st.st_mode & S_IWGRP) { + groupwritable = S_IRGRP|S_IWGRP; + umask(S_IWOTH); + } + } + + alen = strlen(address); + while ((dp = readdir(dir)) != NULL) { + struct stat st; + int rfd, wfd; + char *wrname, *start, *cur, *next, *end, *pos, *posend; + + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) + continue; + + rfd = openat(fd, dp->d_name, O_RDONLY|O_EXLOCK); + if (rfd == -1) + continue; + + if (fstat(rfd, &st) == -1) { + log_err("Impossible to determine the size of %s/%s: %s", + subdir, dp->d_name, strerror(errno)); + close(rfd); + ret = false; + continue; + } + + if (st.st_size == 0) + continue; + if (!S_ISREG(st.st_mode)) { + log_err("Non regular files in subscribers.d"); + continue; + } + + start = mmap(0, st.st_size, PROT_READ, MAP_SHARED, rfd, 0); + if (start == MAP_FAILED) { + log_err("Unable to mmap %s/%s: %s", subdir, dp->d_name, + strerror(errno)); + close(rfd); + ret = false; + continue; + } + + end = start + st.st_size; + + pos = NULL; + for (next = cur = start; next < end; next++) { + if (*next != '\n') + continue; + len = next - cur; + if (strncasecmp(address, cur, len) == 0) { + pos = cur; + break; + } + cur = next + 1; + } + + if (pos == NULL && next > cur) { + len = next - cur; + if (strncasecmp(address, cur, len) == 0) + pos = cur; + } + + /* not found */ + if (pos == NULL) { + munmap(start, st.st_size); + close(rfd); + continue; + } + + /* + * if the file only contains the email to unsubscribe, just + * remove it + */ + posend = pos + alen; + while (posend < end && *posend == '\n') + posend++; + if (pos == start && posend == end) { + unlinkat(fd, dp->d_name, 0); + continue; + } + + xasprintf(&wrname, "%s.new", dp->d_name); + wfd = openat(fd, wrname, O_RDWR|O_CREAT|O_TRUNC|O_EXLOCK, + S_IRUSR|S_IWUSR|groupwritable); + if (wfd == -1) { + log_err("Could not open '%s/%s': %s", subdir, wrname, + strerror(errno)); + munmap(start, st.st_size); + close(rfd); + free(wrname); + ret = false; + continue; + } + + dprintf(wfd, "%.*s", (int)(pos - start), start); + dprintf(wfd, "%.*s", (int) (end - posend), posend); + if (renameat(fd, wrname, fd, dp->d_name) == -1) { + log_err("Could not rename '%s', to '%s'", + wrname, dp->d_name); + ret = false; + } + close(wfd); + munmap(start, st.st_size); + close(rfd); + free(wrname); + } + closedir(dir); + return (ret); +} diff --git a/tests/mlmmj.c b/tests/mlmmj.c index 2798825e..e17879be 100644 --- a/tests/mlmmj.c +++ b/tests/mlmmj.c @@ -73,6 +73,8 @@ ATF_TC_WITHOUT_HEAD(strtotimet); ATF_TC_WITHOUT_HEAD(decode_qp); ATF_TC_WITHOUT_HEAD(parse_lastdigest); ATF_TC_WITHOUT_HEAD(extract_bouncetime); +ATF_TC_WITHOUT_HEAD(open_subscriber_directory); +ATF_TC_WITHOUT_HEAD(unsubscribe); #ifndef NELEM #define NELEM(array) (sizeof(array) / sizeof((array)[0])) @@ -822,6 +824,101 @@ ATF_TC_BODY(extract_bouncetime, tc) free(line); } +ATF_TC_BODY(open_subscriber_directory, tc) +{ + int fd; + const char *dir; + + init_ml(true); + + int dfd = open("list", O_DIRECTORY); + rmdir("list/subscribers.d"); + fd = open_subscriber_directory(dfd, SUB_NORMAL, &dir); + ATF_REQUIRE(fd == -1); + ATF_REQUIRE_STREQ(dir, "subscribers.d"); + mkdir("list/subscribers.d", 0755); + fd = open_subscriber_directory(dfd, SUB_NORMAL, NULL); + ATF_REQUIRE(fd != -1); + close(fd); + + rmdir("list/digesters.d"); + fd = open_subscriber_directory(dfd, SUB_DIGEST, &dir); + ATF_REQUIRE(fd == -1); + ATF_REQUIRE_STREQ(dir, "digesters.d"); + mkdir("list/digesters.d", 0755); + fd = open_subscriber_directory(dfd, SUB_DIGEST, NULL); + ATF_REQUIRE(fd != -1); + close(fd); + + rmdir("list/nomailsubs.d"); + fd = open_subscriber_directory(dfd, SUB_NOMAIL, &dir); + ATF_REQUIRE(fd == -1); + ATF_REQUIRE_STREQ(dir, "nomailsubs.d"); + mkdir("list/nomailsubs.d", 0755); + fd = open_subscriber_directory(dfd, SUB_NOMAIL, NULL); + ATF_REQUIRE(fd != -1); + close(fd); + + fd = open_subscriber_directory(dfd, SUB_ALL, &dir); + ATF_REQUIRE_EQ(fd, -1); + ATF_REQUIRE(dir == NULL); + close(dfd); +} + +ATF_TC_BODY(unsubscribe, tc) +{ + int listfd; + FILE *f; + init_ml(true); + + listfd = open("list", O_DIRECTORY); + + f = fopen("list/subscribers.d/j", "w"); + fputs("john@doe.org", f); + fclose(f); + + f = fopen("list/nomailsubs.d/j", "w"); + fputs("john@doe.org\n", f); + fputs("jane@doe.org\n", f); + fclose(f); + + f = fopen("list/digesters.d/j", "w"); + fputs("john@doe.org\n", f); + fclose(f); + + ATF_REQUIRE(do_unsubscribe(listfd, "john@doe.org", SUB_ALL)); + ATF_REQUIRE_EQ_MSG(access("list/subscribers.d/j", F_OK), -1, "Not unsubscribed"); + ATF_REQUIRE_EQ_MSG(access("list/digesters.d/j", F_OK), -1, "Not unsubscribed from digest"); + if (!atf_utils_compare_file("list/nomailsubs.d/j", "jane@doe.org\n")) { + atf_utils_cat_file("list/nomailsubs.d/j", ">"); + atf_tc_fail("Not unsubscribed from nomail"); + } + + ATF_REQUIRE(do_unsubscribe(listfd, "jane@doe.org", SUB_NOMAIL)); + ATF_REQUIRE_EQ_MSG(access("list/nomailsubs.d/j", F_OK), -1, "Not unsubscribed from nomail"); + + ATF_REQUIRE(!do_unsubscribe(listfd, "plop@meh", SUB_BOTH)); + rmdir("list/subscribers.d"); + ATF_REQUIRE(!do_unsubscribe(listfd, "plop@meh", SUB_NORMAL)); + mkdir("list/subscribers.d", 0755); + + f = fopen("list/nomailsubs.d/j", "w"); + fclose(f); + chmod("list/nomailsubs.d/j", 0111); + ATF_REQUIRE(do_unsubscribe(listfd, "plop@meh", SUB_NOMAIL)); + chmod("list/nomailsubs.d/j", 0644); + ATF_REQUIRE(do_unsubscribe(listfd, "plop@meh", SUB_NOMAIL)); + unlink("list/nomailsubs.d/j"); + + f = fopen("list/nomailsubs.d/j", "w"); + fputs("john@doe.org\n", f); + fputs("jane@doe.org\n", f); + fclose(f); + ATF_REQUIRE(do_unsubscribe(listfd, "plop@meh", SUB_NOMAIL)); + ATF_REQUIRE(!do_unsubscribe(-1, "plop@meh", SUB_ALL)); + +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, random_int); @@ -850,7 +947,8 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, decode_qp); ATF_TP_ADD_TC(tp, parse_lastdigest); ATF_TP_ADD_TC(tp, extract_bouncetime); - + ATF_TP_ADD_TC(tp, open_subscriber_directory); + ATF_TP_ADD_TC(tp, unsubscribe); return (atf_no_error()); }