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"); \
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 \
--- /dev/null
+/*
+ * 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(®exp, rule_ptr,
+ REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
+ regerror(err, ®exp, errbuf, sizeof(errbuf));
+ regfree(®exp);
+ 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(®exp, header->item, 0, NULL, 0)
+ == 0) {
+ match = 1;
+ hdr = header->item;
+ break;
+ }
+ }
+
+ regfree(®exp);
+
+ 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;
+}
+
#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,
}
-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(®exp, rule_ptr,
- REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
- regerror(err, ®exp, errbuf, sizeof(errbuf));
- regfree(®exp);
- 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(®exp, header->item, 0, NULL, 0)
- == 0) {
- match = 1;
- hdr = header->item;
- break;
- }
- }
-
- regfree(®exp);
-
- 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"
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"
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);
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 */
}
}
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)
{
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);
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());
}