]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
Add function to unsubscribe people
authorBaptiste Daroussin <bapt@FreeBSD.org>
Fri, 28 Oct 2022 12:48:55 +0000 (14:48 +0200)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Fri, 28 Oct 2022 12:48:55 +0000 (14:48 +0200)
Add the testsuite that goes with it.

include/mlmmj.h
src/mlmmj.c
tests/mlmmj.c

index 879387c792e9b566c49ba362a3c05ca2c381eb0c..9c4b93eed3919c99e348a1fef3072b0a89ab94f6 100644 (file)
@@ -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; \
index 724c94ae1289211a8f83347d25292f4cdf049f75..4c0d55f67fcb45fc24dbfb693f6c7f1bb56ea09c 100644 (file)
@@ -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);
+}
index 2798825e5448d08d1c4f619095768c1601118af9..e17879be7c9a2a8e30d90d2e2fee7e186a16c0e2 100644 (file)
@@ -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());
 }