]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
access: move code handling access into a separate to make it testable
authorBaptiste Daroussin <bapt@FreeBSD.org>
Tue, 27 Aug 2024 14:33:30 +0000 (16:33 +0200)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Tue, 27 Aug 2024 14:33:30 +0000 (16:33 +0200)
while here, add very basic unit tests

include/mlmmj.h
src/Makefile.am
src/access.c [new file with mode: 0644]
src/mlmmj-process.c
tests/mlmmj.c

index ca352c92e6bd0867519bb66cfd5c7d7d16ae9df2..2aa2a1286304594f29661f883a09418e202912b6 100644 (file)
@@ -138,6 +138,16 @@ bool send_probe(struct ml *ml, const char *addr);
 void parse_content_type(strlist *, char **mime_type, char **boundary);
 char *find_in_list(strlist *, const char *pattern);
 
+typedef enum {
+       ACT_ALLOW = 0,
+       ACT_SEND,
+       ACT_DENY,
+       ACT_MODERATE,
+       ACT_DISCARD,
+} actions_t;
+
+actions_t do_access(strlist *rules, strlist *headers, const char *from, int listfd);
+
 #define MY_ASSERT(expression) if (!(expression)) { \
                        errno = 0; \
                        log_error(LOG_ARGS, "assertion failed"); \
index 28ce1bd64bae905c0ba24dc9608513c328d53e5f..86a80845a059025b78b189c3b764ced97706b34b 100644 (file)
@@ -9,7 +9,7 @@ bin_SCRIPTS = mlmmj-make-ml
 
 EXTRA_DIST = mlmmj-make-ml
 
-libmlmmj_a_SOURCES=    mail-functions.c chomp.c incindexfile.c \
+libmlmmj_a_SOURCES=    access.c mail-functions.c chomp.c incindexfile.c \
                        checkwait_smtpreply.c init_sockfd.c strgen.c \
                        random-int.c print-version.c log_error.c mygetline.c \
                        getaddrsfromfile.c readn.c \
diff --git a/src/access.c b/src/access.c
new file mode 100644 (file)
index 0000000..ad940e2
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <regex.h>
+
+#include "mlmmj.h" 
+#include "log_error.h"
+#include "log_oper.h"
+
+static char *action_strs[] = {
+       "allowed",
+       "sent",
+       "denied",
+       "moderated",
+       "discarded"
+};
+
+actions_t
+do_access(strlist *rules, strlist *headers, const char *from, int listfd)
+{
+       unsigned int match;
+       char *rule_ptr;
+       char errbuf[128];
+       int err;
+       actions_t act;
+       unsigned int not;
+       regex_t regexp;
+       char *hdr;
+       size_t i = 0;
+
+       if (rules == NULL)
+               return (ACT_ALLOW);
+
+       tll_foreach(*rules, it) {
+               rule_ptr = it->item;
+               if (strncmp(rule_ptr, "allow", 5) == 0) {
+                       rule_ptr += 5;
+                       act = ACT_ALLOW;
+               } else if (strncmp(rule_ptr, "send", 4) == 0) {
+                       rule_ptr += 4;
+                       act = ACT_SEND;
+               } else if (strncmp(rule_ptr, "deny", 4) == 0) {
+                       rule_ptr += 4;
+                       act = ACT_DENY;
+               } else if (strncmp(rule_ptr, "moderate", 8) == 0) {
+                       rule_ptr += 8;
+                       act = ACT_MODERATE;
+               } else if (strncmp(rule_ptr, "discard", 7) == 0) {
+                       rule_ptr += 7;
+                       act = ACT_DISCARD;
+               } else {
+                       errno = 0;
+                       log_error(LOG_ARGS, "Unable to parse rule #%d \"%s\":"
+                                       " Missing action keyword. Denying post from \"%s\"",
+                                       i, it->item, from);
+                       log_oper(listfd, OPLOGFNAME, "Unable to parse rule #%d \"%s\":"
+                                       " Missing action keyword. Denying post from \"%s\"",
+                                       i, it->item, from);
+                       return ACT_DENY;
+               }
+
+               if (*rule_ptr == ' ') {
+                       rule_ptr++;
+               } else if (*rule_ptr == '\0') {
+                       /* the rule is a keyword and no regexp */
+                       log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
+                                       " A mail from \"%s\" was %s by rule #%d \"%s\"",
+                                       from, action_strs[act], i, it->item);
+                       return act;
+               } else {
+                       /* we must have space or end of string */
+                       errno = 0;
+                       log_error(LOG_ARGS, "Unable to parse rule #%d \"%s\":"
+                                       " Invalid character after action keyword."
+                                       " Denying post from \"%s\"", i, it->item, from);
+                       log_oper(listfd, OPLOGFNAME, "Unable to parse rule #%d \"%s\":"
+                                       " Invalid character after action keyword."
+                                       " Denying post from \"%s\"", i, it->item, from);
+                       return ACT_DENY;
+               }
+
+               if (*rule_ptr == '!') {
+                       rule_ptr++;
+                       not = 1;
+               } else {
+                       not = 0;
+               }
+
+               /* remove unanchored ".*" from beginning of regexp to stop the
+                * regexp matching to loop so long time it seems like it's
+                * hanging */
+               if (strncmp(rule_ptr, "^.*", 3) == 0) {
+                       rule_ptr += 3;
+               }
+               while (strncmp(rule_ptr, ".*", 2) == 0) {
+                       rule_ptr += 2;
+               }
+
+               if ((err = regcomp(&regexp, rule_ptr,
+                               REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
+                       regerror(err, &regexp, errbuf, sizeof(errbuf));
+                       regfree(&regexp);
+                       errno = 0;
+                       log_error(LOG_ARGS, "regcomp() failed for rule #%d \"%s\""
+                                       " (message: '%s') (expression: '%s')"
+                                       " Denying post from \"%s\"",
+                                       i, it->item, errbuf, rule_ptr, from);
+                       log_oper(listfd, OPLOGFNAME, "regcomp() failed for rule"
+                                       " #%d \"%s\" (message: '%s') (expression: '%s')"
+                                       " Denying post from \"%s\"",
+                                       i, it->item, errbuf, rule_ptr, from);
+                       return ACT_DENY;
+               }
+
+               match = 0;
+               tll_foreach(*headers, header) {
+                       if (regexec(&regexp, header->item, 0, NULL, 0)
+                                       == 0) {
+                               match = 1;
+                               hdr = header->item;
+                               break;
+                       }
+               }
+
+               regfree(&regexp);
+
+               if (match != not) {
+                       if (match) {
+                               log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
+                                               " A mail from \"%s\" with header \"%s\" was %s by"
+                                               " rule #%d \"%s\"", from, hdr, action_strs[act],
+                                               i, it->item);
+                       } else {
+                               log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
+                                               " A mail from \"%s\" was %s by rule #%d \"%s\""
+                                               " because no header matched.", from,
+                                               action_strs[act], i, it->item);
+                       }
+                       return act;
+               }
+               i++;
+       }
+
+       log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
+                       " A mail from \"%s\" didn't match any rules, and"
+                       " was denied by default.", from);
+       return ACT_DENY;
+}
+
index 301d843daba3164938682bfc3baabb42dc751d08..c6ff3e39448aa6428b892fc780b45ec48d8121d5 100644 (file)
 #include "send_help.h"
 #include "send_mail.h"
 
-enum action {
-       ALLOW,
-       SEND,
-       DENY,
-       MODERATE,
-       DISCARD
-};
-
-
-static char *action_strs[] = {
-       "allowed",
-       "sent",
-       "denied",
-       "moderated",
-       "discarded"
-};
-
-
 enum modreason {
        MODNONSUBPOSTS,
        MODNONMODPOSTS,
@@ -225,135 +207,6 @@ static void newmoderated(struct ml *ml, const char *mailfilename,
 }
 
 
-static enum action do_access(strlist *rule_strs, strlist *hdrs,
-               const char *from, int listfd)
-{
-       unsigned int match;
-       char *rule_ptr;
-       char errbuf[128];
-       int err;
-       enum action act;
-       unsigned int not;
-       regex_t regexp;
-       char *hdr;
-       size_t i = 0;
-
-       tll_foreach(*rule_strs, it) {
-               rule_ptr = it->item;
-               if (strncmp(rule_ptr, "allow", 5) == 0) {
-                       rule_ptr += 5;
-                       act = ALLOW;
-               } else if (strncmp(rule_ptr, "send", 4) == 0) {
-                       rule_ptr += 4;
-                       act = SEND;
-               } else if (strncmp(rule_ptr, "deny", 4) == 0) {
-                       rule_ptr += 4;
-                       act = DENY;
-               } else if (strncmp(rule_ptr, "moderate", 8) == 0) {
-                       rule_ptr += 8;
-                       act = MODERATE;
-               } else if (strncmp(rule_ptr, "discard", 7) == 0) {
-                       rule_ptr += 7;
-                       act = DISCARD;
-               } else {
-                       errno = 0;
-                       log_error(LOG_ARGS, "Unable to parse rule #%d \"%s\":"
-                                       " Missing action keyword. Denying post from \"%s\"",
-                                       i, it->item, from);
-                       log_oper(listfd, OPLOGFNAME, "Unable to parse rule #%d \"%s\":"
-                                       " Missing action keyword. Denying post from \"%s\"",
-                                       i, it->item, from);
-                       return DENY;
-               }
-
-               if (*rule_ptr == ' ') {
-                       rule_ptr++;
-               } else if (*rule_ptr == '\0') {
-                       /* the rule is a keyword and no regexp */
-                       log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
-                                       " A mail from \"%s\" was %s by rule #%d \"%s\"",
-                                       from, action_strs[act], i, it->item);
-                       return act;
-               } else {
-                       /* we must have space or end of string */
-                       errno = 0;
-                       log_error(LOG_ARGS, "Unable to parse rule #%d \"%s\":"
-                                       " Invalid character after action keyword."
-                                       " Denying post from \"%s\"", i, it->item, from);
-                       log_oper(listfd, OPLOGFNAME, "Unable to parse rule #%d \"%s\":"
-                                       " Invalid character after action keyword."
-                                       " Denying post from \"%s\"", i, it->item, from);
-                       return DENY;
-               }
-
-               if (*rule_ptr == '!') {
-                       rule_ptr++;
-                       not = 1;
-               } else {
-                       not = 0;
-               }
-
-               /* remove unanchored ".*" from beginning of regexp to stop the
-                * regexp matching to loop so long time it seems like it's
-                * hanging */
-               if (strncmp(rule_ptr, "^.*", 3) == 0) {
-                       rule_ptr += 3;
-               }
-               while (strncmp(rule_ptr, ".*", 2) == 0) {
-                       rule_ptr += 2;
-               }
-
-               if ((err = regcomp(&regexp, rule_ptr,
-                               REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
-                       regerror(err, &regexp, errbuf, sizeof(errbuf));
-                       regfree(&regexp);
-                       errno = 0;
-                       log_error(LOG_ARGS, "regcomp() failed for rule #%d \"%s\""
-                                       " (message: '%s') (expression: '%s')"
-                                       " Denying post from \"%s\"",
-                                       i, it->item, errbuf, rule_ptr, from);
-                       log_oper(listfd, OPLOGFNAME, "regcomp() failed for rule"
-                                       " #%d \"%s\" (message: '%s') (expression: '%s')"
-                                       " Denying post from \"%s\"",
-                                       i, it->item, errbuf, rule_ptr, from);
-                       return DENY;
-               }
-
-               match = 0;
-               tll_foreach(*hdrs, header) {
-                       if (regexec(&regexp, header->item, 0, NULL, 0)
-                                       == 0) {
-                               match = 1;
-                               hdr = header->item;
-                               break;
-                       }
-               }
-
-               regfree(&regexp);
-
-               if (match != not) {
-                       if (match) {
-                               log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
-                                               " A mail from \"%s\" with header \"%s\" was %s by"
-                                               " rule #%d \"%s\"", from, hdr, action_strs[act],
-                                               i, it->item);
-                       } else {
-                               log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
-                                               " A mail from \"%s\" was %s by rule #%d \"%s\""
-                                               " because no header matched.", from,
-                                               action_strs[act], i, it->item);
-                       }
-                       return act;
-               }
-               i++;
-       }
-
-       log_oper(listfd, OPLOGFNAME, "mlmmj-process: access -"
-                       " A mail from \"%s\" didn't match any rules, and"
-                       " was denied by default.", from);
-       return DENY;
-}
-
 static void print_help(const char *prg)
 {
        printf("Usage: %s -L /path/to/list\n"
@@ -750,12 +603,12 @@ int main(int argc, char **argv)
 
        access_rules = ctrlvalues(ml.ctrlfd, "access");
        if (access_rules != NULL) {
-               enum action accret;
+               actions_t accret;
                /* Don't send a mail about denial to the list, but silently
                 * discard and exit. Also do this in case it's turned off */
                accret = do_access(access_rules, &allheaders,
                                        posteraddr, ml.fd);
-               if (accret == DENY) {
+               if (accret == ACT_DENY) {
                        if ((strcasecmp(ml.addr, posteraddr) == 0) ||
                                    statctrl(ml.ctrlfd, "noaccessdenymails")) {
                                log_error(LOG_ARGS, "Discarding %s because"
@@ -783,10 +636,10 @@ int main(int argc, char **argv)
                        free(donemailname);
                        free(randomstr);
                        send_help(&ml, queuefilename, posteraddr);
-               } else if (accret == MODERATE) {
+               } else if (accret == ACT_MODERATE) {
                        moderated = 1;
                        modreason = ACCESS;
-               } else if (accret == DISCARD) {
+               } else if (accret == ACT_DISCARD) {
                        xasprintf(&discardname, "%s/queue/discarded/%s", ml.dir,
                            randomstr);
                        free(randomstr);
@@ -800,9 +653,9 @@ int main(int argc, char **argv)
                        free(donemailname);
                        free(discardname);
                        exit(EXIT_SUCCESS);
-               } else if (accret == SEND) {
+               } else if (accret == ACT_SEND) {
                        send = 1;
-               } else if (accret == ALLOW) {
+               } else if (accret == ACT_ALLOW) {
                        /* continue processing as normal */
                }
        }
index c7c43889cc03f0e1aa63cb36e1f728718adbc354..09a4a66d8f3c2750c18f9b47e5865930a97ebf69 100644 (file)
@@ -160,6 +160,7 @@ ATF_TC_WITHOUT_HEAD(mod_get_addr_type);
 ATF_TC_WITHOUT_HEAD(send_probe);
 ATF_TC_WITHOUT_HEAD(parse_content_type);
 ATF_TC_WITHOUT_HEAD(find_in_list);
+ATF_TC_WITHOUT_HEAD(do_access);
 
 ATF_TC_BODY(random_int, tc)
 {
@@ -2923,6 +2924,27 @@ ATF_TC_BODY(parse_content_type, tc)
        free(b);
 }
 
+ATF_TC_BODY(do_access, tc)
+{
+       int listfd = -1;
+       strlist rules = tll_init();
+       strlist headers = tll_init();
+       tll_push_back(rules, "allow");
+
+       ATF_REQUIRE_EQ(do_access(NULL, NULL, "test@plop", listfd), ACT_ALLOW);
+       ATF_REQUIRE_EQ(do_access(&rules, NULL, "test@plop", listfd), ACT_ALLOW);
+       tll_pop_back(rules);
+       tll_push_back(rules, "deny");
+       ATF_REQUIRE_EQ(do_access(&rules, NULL, "test@plop", listfd), ACT_DENY);
+       tll_pop_back(rules);
+       tll_push_back(rules, "crap");
+       ATF_REQUIRE_EQ(do_access(&rules, NULL, "test@plop", listfd), ACT_DENY);
+       tll_pop_back(rules);
+       tll_push_back(headers, "from: toto@bla");
+       tll_push_back(rules, "moderate ^from: toto@");
+       ATF_REQUIRE_EQ(do_access(&rules, &headers, "test@plop", listfd), ACT_MODERATE);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
        ATF_TP_ADD_TC(tp, random_int);
@@ -3012,6 +3034,7 @@ ATF_TP_ADD_TCS(tp)
        ATF_TP_ADD_TC(tp, send_probe);
        ATF_TP_ADD_TC(tp, parse_content_type);
        ATF_TP_ADD_TC(tp, find_in_list);
+       ATF_TP_ADD_TC(tp, do_access);
 
        return (atf_no_error());
 }