mmmail:
authorMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 14 Apr 2020 13:29:15 +0000 (13:29 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 14 Apr 2020 13:29:15 +0000 (13:29 +0000)
- Bumped revision to 0.5.0
- Added new SQL tables nohop, nores and noheader to easily configure
  exceptions
- Fixed a potential race condition in mmserver in relation to cached
  address/hostname/connections object nodes and the collector

mmsoftware/mmlib/mmserver.c
mmsoftware/mmmail/scripts/pgsql-tables.sql
mmsoftware/mmmail/scripts/upgrade-0.5.0.sql [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.h
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.c
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.h

index 80ce357..5380ce3 100644 (file)
@@ -402,6 +402,8 @@ tcp_server(char *message, char *server_names, char *listen_ips, uid_t uid,
                    addrl = SERVER_SOCKADDR_SOCKLEN(&addr);
                    if ((msgsock = accept(fds[i].fd, SERVER_SOCKADDR(&addr),
                                    &addrl)) != -1) {
+                       bool    cached = true;
+
                        /* Make sure that we respect connection and rate
                         * limits.
                         */
@@ -412,12 +414,13 @@ tcp_server(char *message, char *server_names, char *listen_ips, uid_t uid,
                                        &ctable, &addr,
                                        sizeof(struct server_sockaddr)))
                                == NULL) {
+                           cached = false;
                            /* Create new node */
                            if (HASHTABLE_NODES(&ctable) < maxips) {
                                if ((clnode = (clientlistnode *)pool_alloc(
                                                &cpool, false)) != NULL) {
                                    clnode->hostname = NULL;
-                                   clnode->connections = 0;
+                                   clnode->connections = 1;
                                    if (ratemax != 0)
                                        LR_INIT(&clnode->lr, ratemax,
                                                (time_t)rateper, curtime);
@@ -442,7 +445,8 @@ tcp_server(char *message, char *server_names, char *listen_ips, uid_t uid,
                            /* Either we found an existing node or we created
                             * it successfully
                             */
-                           if (clnode->connections < maxperip) {
+                           if ((clnode->connections < maxperip) ||
+                               (!cached && clnode->connections == maxperip)) {
                                if (ratemax != 0) {
                                    if (!(ok = lr_allow(&clnode->lr, 1, 0,
                                                    false)))
@@ -470,9 +474,14 @@ tcp_server(char *message, char *server_names, char *listen_ips, uid_t uid,
                                phi->iface = fdsi[i].iface;
                                if (pthread_object_call(NULL, phandleclient,
                                            phi) == 0) {
-                                   pthread_mutex_lock(&ctable_lock);
-                                   clnode->connections++;
-                                   pthread_mutex_unlock(&ctable_lock);
+                                   /* Already at 1 if non-cached to avoid a
+                                    * race condition with collector
+                                    */
+                                   if (cached) {
+                                       pthread_mutex_lock(&ctable_lock);
+                                       clnode->connections++;
+                                       pthread_mutex_unlock(&ctable_lock);
+                                   }
                                } else {
                                    if (phi != NULL) {
                                        pthread_mutex_lock(&ppool_lock);
@@ -490,6 +499,9 @@ tcp_server(char *message, char *server_names, char *listen_ips, uid_t uid,
                        } else {
                            char ipaddr[64];
 
+                           if (cached)
+                                   clnode->connections = 0;
+
                            /* Close down connection with client
                             * immediately, sending message.
                             */
@@ -1534,6 +1546,8 @@ clnode_expire_thread_iterator(hashnode_t *hnod, void *udata)
        if (clnode->connections == 0) {
            /* Safe to expunge this node from the cache */
            hashtable_unlink(&ctable, (hashnode_t *)clnode);
+           if (clnode->hostname != NULL)
+               clnode->hostname = mmstrfree(clnode->hostname);
            pool_free((pnode_t *)clnode);
        } else {
            /* Reset it */
index 3f916d8..8c2db25 100644 (file)
@@ -32,6 +32,31 @@ CREATE TABLE "nofrom" (
 );
 
 ---
+--- Address/domain patterns we allow mail without hops from
+---
+CREATE TABLE "nohop" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Address/domain patterns we allow mail without reverse resolve from
+---
+CREATE TABLE "nores" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Header/patterns which if matching cause a post to be rejected
+---
+CREATE TABLE "noheader" (
+       header          varchar(64)     NOT NULL,
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (header, pattern)
+);
+
+---
 --- Domains that are considered local (hosted by this server)
 ---
 CREATE TABLE "relaylocal" (
@@ -299,7 +324,8 @@ COMMIT;
 --- Give necessary privileges to mmmail user
 ---
 GRANT ALL ON TABLE alias, box, file_gc_queue, filter, mail, folder, nofrom,
-       relayfrom, relaylocal, relayqueue, session, "user" TO mmmail;
+       nohop, nores, noheader, relayfrom, relaylocal, relayqueue, session,
+       "user" TO mmmail;
 GRANT ALL ON file_gc_queue_id_seq, mail_id_seq, relayqueue_id_seq TO mmmail;
 GRANT EXECUTE ON FUNCTION box_delete(), mail_add(), mail_delete(),
        relayqueue_delete(), user_update(varchar, varchar) TO mmmail;
diff --git a/mmsoftware/mmmail/scripts/upgrade-0.5.0.sql b/mmsoftware/mmmail/scripts/upgrade-0.5.0.sql
new file mode 100644 (file)
index 0000000..9e4478b
--- /dev/null
@@ -0,0 +1,35 @@
+# $Id$
+#
+# You should execute this script if you are upgrading mmmail from 0.4.0 or
+# older to 0.5.0 or later, to add new configuration tables.
+#
+
+---
+--- Address/domain patterns we allow mail without hops from
+---
+CREATE TABLE "nohop" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Address/domain patterns we allow mail without reverse resolve from
+---
+CREATE TABLE "nores" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Header/patterns which if matching cause a post to be rejected
+---
+CREATE TABLE "noheader" (
+       header          varchar(64)     NOT NULL,
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (header, pattern)
+);
+
+---     
+--- Give necessary privileges to mmmail user
+--- 
+GRANT ALL ON TABLE nohop, nores, noheader TO mmmail;
index cb276ad..fbfc946 100644 (file)
@@ -68,7 +68,7 @@
 /* DEFINITIONS */
 
 #define DAEMON_NAME    "mmpop3d"
-#define DAEMON_VERSION "mmmail-0.4.0"
+#define DAEMON_VERSION "mmmail-0.5.0"
 
 /* Negative states are used by the state swapper, others are real states */
 #define STATE_ERROR    -3
index f965fa2..7a2f992 100644 (file)
@@ -730,7 +730,8 @@ all_mail(clientenv *clenv)
     /* Allow nofrom list to avoid HELO even if required */
     if (CONF.REQUIRE_HELO) {
        if (clenv->helo == NULL) {
-           nofrom = check_nofrom(clenv);
+           /* Exceptions */
+           nofrom = check_match(clenv, "nofrom", NULL);
            checkednofrom = true;
            if (!nofrom) {
                if (CONF.STATFAIL_NOHELO)
@@ -754,7 +755,7 @@ all_mail(clientenv *clenv)
             * of envelope based filtering for this post.
             */
            if (!checkednofrom)
-               nofrom = check_nofrom(clenv);
+               nofrom = check_match(clenv, "nofrom", NULL);
            if ((valid = nofrom))
                *addr = '\0';
            clenv->nofrom = true;
@@ -1313,25 +1314,70 @@ check_alias(clientenv *clenv, char *addr)
 }
 
 
+/* Checks in the list of forbidden header/data-patterns for a match.
+ * Returns true on match, or false.
+ */
+static bool
+check_header_match(clientenv *clenv, const char *header, const char *data)
+{
+    bool       res = false;
+    PGresult   *pgres;
+    const char *params[2];
+
+    params[0] = header;
+    params[1] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "SELECT pattern FROM noheader WHERE header=$1", 1, NULL, params,
+           NULL, NULL, 0)) != NULL) {
+       int             i, t;
+       const char      *v = NULL;
+
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           v = PQgetvalue(pgres, i, 0);
+           if (best_match(data, v) != -1) {
+               mmsyslog(0, LOGLEVEL,
+                   "%08lX Header %s matched forbidden pattern %s (%s)",
+                   clenv->id, header, v, data);
+               res = true;
+               break;
+           }
+       }
+       PQclear(pgres);
+    }
+
+    return res;
+}
+
+
 /* Depending on which is set of <addr> and/or <host>, returns true if any
  * of both matched an entry.
  */
 static bool
-check_nofrom(clientenv *clenv)
+check_match(clientenv *clenv, const char *table, const char *optstr)
 {
     bool       res = false;
     PGresult   *pgres;
+    char       query[64];
 
-    if (clenv->c_ipaddr == NULL && clenv->c_hostname == NULL)
-       return (false);
+    if (optstr == NULL) {
+           if (clenv->c_ipaddr == NULL && clenv->c_hostname == NULL)
+                   return (false);
+    }
 
-    if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM nofrom"))
-           != NULL) {
+    snprintf(query, 63, "SELECT pattern FROM %s", table);
+    if ((pgres = PQexec(clenv->pgconn, query)) != NULL) {
        int     i, t;
        char    *pat;
 
        for (i = 0, t = PQntuples(pgres); i < t; i++) {
            pat = PQgetvalue(pgres, i, 0);
+           if (optstr != NULL) {
+               if ((best_match(optstr, pat)) != -1) {
+                   res = true;
+                   break;
+               }
+               continue;
+           }
            if (clenv->c_ipaddr != NULL) {
                if ((best_match(clenv->c_ipaddr, pat)) != -1) {
                    res = true;
@@ -1578,10 +1624,8 @@ valid_host(clientenv *clenv, char *host, int res, bool addr, bool sanity)
     if (res != HOST_NORES) {
        char answer[128];
 
-       /* XXX Should ideally use the database for such exceptions, but we
-        * needed this quick fix for now
-        */
-       if (best_match(host, "*dreamhost*") != -1)
+       /* Exceptions */
+       if (check_match(clenv, "nores", host))
                return true;
 
        if (res == HOST_RES_MX) {
@@ -1678,51 +1722,14 @@ validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
            mm_strupper(header);
        }
 
-       /* Block some ad software which have identifyable Message-ID */
-       if (strcmp(header, "MESSAGE-ID") == 0) {
-           if (best_match(data, "*adehost*") != -1) {
-               *res = CFDBRB_HEADER;
-               return FDBRB_STOP;
-           }
-           if (best_match(data, "*agenceweb*") != -1) {
-               *res = CFDBRB_HEADER;
-               return FDBRB_STOP;
-           }
-       }
-
-       /* XXX
-        * Permit admin-supplied table to filter unwanted headers here.
-        */
-       /* Drop popular spam agent */
-       if (strcmp(header, "X-MAILER") == 0) {
-           if (best_match(data, "*the*bat*") != -1) {
-               *res = CFDBRB_HEADER;
-               return FDBRB_STOP;
-           }
-       }
-       /* Drop html-only mail */
-       /*
-       if (strcmp(header, "CONTENT-TYPE") == 0) {
-           if (best_match(data, "*text/html*") != -1) {
+       /* Check if a forbidden header/pattern should reject the post */
+       if (check_header_match(ud->clenv, header, data)) {
                *res = CFDBRB_HEADER;
                return FDBRB_STOP;
-           }
        }
-       */
 
        /* Count number of Received: headers (SMTP hops) */
        if (strcmp(header, "RECEIVED") == 0) {
-           /* Drop PowerMTA(TM) processed wintendo mail */
-           if (best_match(data, "*powermta*") != -1) {
-               *res = CFDBRB_HEADER;
-               return FDBRB_STOP;
-           }
-           /* Drop a local mailing lists spammer */
-           if (best_match(data, "*bathge*") != -1) {
-               *res = CFDBRB_HEADER;
-               return FDBRB_STOP;
-           }
-
            ud->hops++;
            if (ud->hops > CONF.MAX_HOPS) {
                /* Exceeded allowed number of hops, cancel reception */
@@ -1782,12 +1789,8 @@ endheader:
 
     /* Drop if we require at least one hop but got none */
     if (CONF.REQUIRE_HOP && ud->hops == 0) {
-       /*
-        * XXX Apple's ITunes password recovery provides no Received: line,
-        * like spam...
-        */
-       if (best_match(ud->clenv->from, "*@itunes.com") == -1 &&
-           best_match(ud->clenv->from, "*@*apple.com") == -1) {
+       /* Exceptions */
+       if (!check_match(ud->clenv, "nohop", ud->clenv->from)) {
            *res = CFDBRB_NOHOP;
            return FDBRB_STOP;
        }
index b50571c..0d7a264 100644 (file)
@@ -68,7 +68,7 @@
 
 /* DEFINITIONS */
 #define DAEMON_NAME    "mmsmtpd"
-#define DAEMON_VERSION "mmmail-0.4.0"
+#define DAEMON_VERSION "mmmail-0.5.0"
 
 /* Negative states are used by the state swapper, others are real states */
 #define STATE_ERROR    -3
@@ -299,7 +299,8 @@ static bool init_clientenv(clientenv *, bool);
 static clientenv *free_clientenv(clientenv *);
 static void empty_rcpts(list_t *);
 static bool check_alias(clientenv *, char *);
-static bool check_nofrom(clientenv *);
+static bool check_match(clientenv *, const char *, const char *);
+static bool check_header_match(clientenv *, const char *, const char *);
 static int lock_check(const char *);
 static int best_match(const char *, const char *);
 static bool local_address(clientenv *, struct box_info *, const char *);