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);
+}
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]))
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);
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());
}