Merge branch 'with-dns-bl'
authorFlorent Bruneau <florent.bruneau@polytechnique.org>
Sun, 5 Oct 2008 16:13:44 +0000 (18:13 +0200)
committerFlorent Bruneau <florent.bruneau@polytechnique.org>
Sun, 5 Oct 2008 16:13:44 +0000 (18:13 +0200)
common/Makefile
common/array.h
common/rbl.c [new file with mode: 0644]
common/rbl.h [new file with mode: 0644]
example/postlicyd.conf
postlicyd/Makefile
postlicyd/config.c
postlicyd/iplist.c [moved from postlicyd/rbl.c with 83% similarity]
postlicyd/iplist.h [moved from postlicyd/rbl.h with 98% similarity]
postlicyd/strlist.c
postlicyd/tst-rbl.c

index 541d5ac..3f30e73 100644 (file)
@@ -32,7 +32,7 @@
 LIBS  = lib
 TESTS = tst-trie
 
-lib_SOURCES = str.c buffer.c common.c epoll.c server.c trie.c file.c
+lib_SOURCES = str.c buffer.c common.c epoll.c server.c trie.c file.c rbl.c
 tst-trie_SOURCES = tst-trie.c lib.a
 
 all:
index 3a8fda5..6f5217d 100644 (file)
         array_ensure_capacity_delta(array, 1);                                 \
         (array).data[(array).len++] = (obj);                                   \
     } while (0)
-#define array_append(array, objs, len)                                         \
+#define array_append(array, objs, Len)                                         \
     do {                                                                       \
-        const typeof((array).len) __len = (len);                               \
+        const typeof((array).len) __len = (Len);                               \
         array_ensure_capacity_delta(array, __len);                             \
         memcpy((array).data + (array).len, objs,                               \
                __len * sizeof(*(array).data));                                 \
         array_ensure_can_edit(array);                                          \
         p_shrink(&(array).data, (array).len, &(array).size);                   \
     } while (0)
-#define array_elt(array, n) (array).data[(n)]
-#define array_ptr(array, n) (array).data + (n)
+#define array_elt(array, n) ((array).data[(n)])
+#define array_ptr(array, n) ((array).data + (n))
 
 #define foreach(var, array)                                                    \
     for (uint32_t __Ai = 0 ; __Ai < (array).len ; ++__Ai) {                    \
diff --git a/common/rbl.c b/common/rbl.c
new file mode 100644 (file)
index 0000000..0ae4979
--- /dev/null
@@ -0,0 +1,70 @@
+/******************************************************************************/
+/*          pfixtools: a collection of postfix related tools                  */
+/*          ~~~~~~~~~                                                         */
+/*  ________________________________________________________________________  */
+/*                                                                            */
+/*  Redistribution and use in source and binary forms, with or without        */
+/*  modification, are permitted provided that the following conditions        */
+/*  are met:                                                                  */
+/*                                                                            */
+/*  1. Redistributions of source code must retain the above copyright         */
+/*     notice, this list of conditions and the following disclaimer.          */
+/*  2. Redistributions in binary form must reproduce the above copyright      */
+/*     notice, this list of conditions and the following disclaimer in the    */
+/*     documentation and/or other materials provided with the distribution.   */
+/*  3. The names of its contributors may not be used to endorse or promote    */
+/*     products derived from this software without specific prior written     */
+/*     permission.                                                            */
+/*                                                                            */
+/*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
+/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
+/*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
+/*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
+/*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
+/*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
+/*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
+/*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
+/*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
+/*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
+/*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
+/******************************************************************************/
+
+/*
+ * Copyright © 2008 Florent Bruneau
+ */
+
+#include <netdb.h>
+#include "rbl.h"
+
+static inline rbl_result_t rbl_dns_check(const char *hostname)
+{
+    debug("looking up for %s", hostname);
+    struct hostent *host = gethostbyname(hostname);
+    if (host != NULL) {
+        debug("host found");
+        return RBL_FOUND;
+    } else {
+        if (h_errno == HOST_NOT_FOUND) {
+            debug("host not found: %s", hostname);
+            return RBL_NOTFOUND;
+        }
+        debug("dns error: %m");
+        return RBL_ERROR;
+    }
+}
+
+rbl_result_t rbl_check(const char *rbl, uint32_t ip)
+{
+    char host[257];
+    snprintf(host, 257, "%d.%d.%d.%d.%s",
+             ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff,
+             rbl);
+    return rbl_dns_check(host);
+}
+
+rbl_result_t rhbl_check(const char *rhbl, const char *hostname)
+{
+    char host[257];
+    snprintf(host, 257, "%s.%s", hostname, rhbl);
+    return rbl_dns_check(host);
+}
diff --git a/common/rbl.h b/common/rbl.h
new file mode 100644 (file)
index 0000000..4a1e763
--- /dev/null
@@ -0,0 +1,57 @@
+/******************************************************************************/
+/*          pfixtools: a collection of postfix related tools                  */
+/*          ~~~~~~~~~                                                         */
+/*  ________________________________________________________________________  */
+/*                                                                            */
+/*  Redistribution and use in source and binary forms, with or without        */
+/*  modification, are permitted provided that the following conditions        */
+/*  are met:                                                                  */
+/*                                                                            */
+/*  1. Redistributions of source code must retain the above copyright         */
+/*     notice, this list of conditions and the following disclaimer.          */
+/*  2. Redistributions in binary form must reproduce the above copyright      */
+/*     notice, this list of conditions and the following disclaimer in the    */
+/*     documentation and/or other materials provided with the distribution.   */
+/*  3. The names of its contributors may not be used to endorse or promote    */
+/*     products derived from this software without specific prior written     */
+/*     permission.                                                            */
+/*                                                                            */
+/*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
+/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
+/*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
+/*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
+/*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
+/*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
+/*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
+/*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
+/*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
+/*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
+/*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
+/******************************************************************************/
+
+/*
+ * Copyright © 2008 Florent Bruneau
+ */
+
+#ifndef PFIXTOOLS_RBL_H
+#define PFIXTOOLS_RBL_H
+
+#include "common.h"
+
+typedef enum {
+  RBL_ERROR,
+  RBL_FOUND,
+  RBL_NOTFOUND,
+} rbl_result_t;
+
+/** Check the presence of the given IP in the given rbl.
+ */
+__attribute__((nonnull(1)))
+rbl_result_t rbl_check(const char *rbl, uint32_t ip);
+
+/** Check the presence of the given hostname in the given rhbl.
+ */
+__attribute__((nonnull(1,2)))
+rbl_result_t rhbl_check(const char *rhbl, const char *hostname);
+
+#endif
index 12d8ff5..57c5966 100644 (file)
@@ -72,6 +72,9 @@
 #             score of the IP
 #           - rbldns: (no)?lock:weight:filename
 #             this is an alias for file.
+#           - dns: weight:hostname
+#             use a rbl via DNS resolution with the given weight. If a DNS lookup error occurs
+#             the IP is considered as beeing "not found".
 #           - soft_threshold: score (default: 1)
 #             minimum score to match the soft_match return value
 #           - hard_threshold: score (default: 1)
@@ -79,6 +82,7 @@
 #        Return value:
 #          The score of a query is the sum of the weight of the blacklist it matched.
 #           - If the IP can not be parsed, returns error
+#           - If no rbl was available (no file and all dns down), returns error.
 #           - If the score is strictly greater >= than hard_threshold, returns hard_match
 #           - If the score is strictly greater >= than soft_threshold, returns soft_match
 #           - Else, returns fail
@@ -125,6 +129,10 @@ spamhaus_and_abuseat {
 #               * names beginning with '*' are sorted as 'domains' and are matched as suffix
 #               * names starting with an alphanumirical character are sorted as 'hostnames' and are
 #                process via exact matching.
+#           - dns: weight:hostname
+#             use a rhbl via DNS resolution with the given weight. If a DNS lookup error occurs
+#             the hostname is considered as beeing "not found". This can only be used with "hostnames"
+#             typed fields.
 #           - soft_threshold: score (default: 1)
 #             minimum score to match the soft_match return value
 #           - hard_threshold: score (default: 1)
@@ -140,6 +148,7 @@ spamhaus_and_abuseat {
 #             No space is allowed in this parameter.
 #        Return value:
 #          The score of a query is the sum of the weight of the list it matched.
+#           - If no rhbl was available (no file and all dns down), returns error.
 #           - If the score is strictly greater >= than hard_threshold, returns hard_match
 #           - If the score is strictly greater >= than soft_threshold, returns soft_match
 #           - Else, returns fail
index 363a9a0..0983841 100644 (file)
@@ -38,12 +38,12 @@ GENERATED = policy_tokens.h policy_tokens.c \
                                                param_tokens.h param_tokens.c
 TESTS     = tst-rbl tst-filters
 
-FILTERS                = rbl.c greylist.c strlist.c match.c
+FILTERS                = iplist.c greylist.c strlist.c match.c
 
 postlicyd_SOURCES = main-postlicyd.c ../common/lib.a filter.c config.c query.c $(FILTERS) $(GENERATED)
 postlicyd_LIBADD  = $(TC_LIBS)
 
-tst-rbl_SOURCES   = tst-rbl.c ../common/lib.a filter.c config.c query.c rbl.c $(GENERATED)
+tst-rbl_SOURCES   = tst-rbl.c ../common/lib.a filter.c config.c query.c iplist.c $(GENERATED)
 tst-filters_SOURCES = tst-filters.c ../common/lib.a config.c filter.c query.c $(FILTERS) $(GENERATED)
 tst-filters_LIBADD  = $(TC_LIBS)
 
index d4131f1..f2b8dbe 100644 (file)
@@ -423,7 +423,7 @@ config_t *config_read(const char *file)
     config_t *config = config_new();
     config->filename = file;
     if (!config_reload(config)) {
-        p_delete(&config);
+        config_delete(&config);
         return NULL;
     }
     return config;
similarity index 83%
rename from postlicyd/rbl.c
rename to postlicyd/iplist.c
index 4e1ac27..5337dca 100644 (file)
 #include <sys/mman.h>
 
 #include "common.h"
-#include "rbl.h"
+#include "iplist.h"
 #include "str.h"
 #include "file.h"
 #include "array.h"
+#include "rbl.h"
 
 #define IPv4_BITS        5
 #define IPv4_PREFIX(ip)  ((uint32_t)(ip) >> IPv4_BITS)
@@ -227,6 +228,9 @@ bool rbldb_ipv4_lookup(const rbldb_t *db, uint32_t ip)
 typedef struct rbl_filter_t {
     PA(rbldb_t) rbls;
     A(int)      weights;
+    A(char)     hosts;
+    A(int)      host_offsets;
+    A(int)      host_weights;
 
     int32_t     hard_threshold;
     int32_t     soft_threshold;
@@ -242,6 +246,9 @@ static void rbl_filter_delete(rbl_filter_t **rbl)
     if (*rbl) {
         array_deep_wipe((*rbl)->rbls, rbldb_delete);
         array_wipe((*rbl)->weights);
+        array_wipe((*rbl)->hosts);
+        array_wipe((*rbl)->host_offsets);
+        array_wipe((*rbl)->host_weights);
         p_delete(rbl);
     }
 }
@@ -318,6 +325,39 @@ static bool rbl_filter_constructor(filter_t *filter)
             }
           } break;
 
+          /* dns parameter.
+           *  weight:hostname.
+           * define a RBL to use through DNS resolution.
+           */
+          case ATK_DNS: {
+            int  weight = 0;
+            const char *current = param->value;
+            const char *p = m_strchrnul(param->value, ':');
+            char *next = NULL;
+            for (int i = 0 ; i < 2 ; ++i) {
+                PARSE_CHECK(i == 1 || *p,
+                            "host parameter must contains a weight option");
+                switch (i) {
+                  case 0:
+                    weight = strtol(current, &next, 10);
+                    PARSE_CHECK(next == p && weight >= 0 && weight <= 1024,
+                                "illegal weight value %.*s",
+                                (p - current), current);
+                    break;
+
+                  case 1:
+                    array_add(data->host_offsets, array_len(data->hosts));
+                    array_append(data->hosts, current, strlen(current) + 1);
+                    array_add(data->host_weights, weight);
+                    break;
+                }
+                if (i != 1) {
+                    current = p + 1;
+                    p = m_strchrnul(current, ':');
+                }
+            }
+          } break;
+
           /* hard_threshold parameter is an integer.
            *  If the matching score is greater or equal than this threshold,
            *  the hook "hard_match" is called.
@@ -338,7 +378,7 @@ static bool rbl_filter_constructor(filter_t *filter)
         }
     }}
 
-    PARSE_CHECK(data->rbls.len
+    PARSE_CHECK(data->rbls.len || data->host_offsets.len,
                 "no file parameter in the filter %s", filter->name);
     filter->data = data;
     return true;
@@ -357,6 +397,7 @@ static filter_result_t rbl_filter(const filter_t *filter, const query_t *query)
     int32_t sum = 0;
     const char *end = NULL;
     const rbl_filter_t *data = filter->data;
+    bool  error = true;
 
     if (parse_ipv4(query->client_address, &end, &ip) != 0) {
         warn("invalid client address: %s, expected ipv4",
@@ -368,7 +409,34 @@ static filter_result_t rbl_filter(const filter_t *filter, const query_t *query)
         int weight   = array_elt(data->weights, i);
         if (rbldb_ipv4_lookup(rbl, ip)) {
             sum += weight;
+            if (sum >= data->hard_threshold) {
+                return HTK_HARD_MATCH;
+            }
         }
+        error = false;
+    }
+    for (uint32_t i = 0 ; i < data->host_offsets.len ; ++i) {
+        const char *rbl = array_ptr(data->hosts, array_elt(data->host_offsets, i));
+        int weight      = array_elt(data->host_weights, i);
+        switch (rbl_check(rbl, ip)) {
+          case RBL_FOUND:
+            error = false;
+            sum += weight;
+            if (sum >= data->hard_threshold) {
+                return HTK_HARD_MATCH;
+            }
+            break;
+          case RBL_NOTFOUND:
+            error = false;
+            break;
+          case RBL_ERROR:
+            warn("rbl %s unavailable", rbl);
+            break;
+        }
+    }
+    if (error) {
+        err("filter %s: all the rbl returned an error", filter->name);
+        return HTK_ERROR;
     }
     if (sum >= data->hard_threshold) {
         return HTK_HARD_MATCH;
@@ -395,6 +463,7 @@ static int rbl_init(void)
      */
     (void)filter_param_register(type, "file");
     (void)filter_param_register(type, "rbldns");
+    (void)filter_param_register(type, "dns");
     (void)filter_param_register(type, "hard_threshold");
     (void)filter_param_register(type, "soft_threshold");
     return 0;
similarity index 98%
rename from postlicyd/rbl.h
rename to postlicyd/iplist.h
index dc9a03d..45a0088 100644 (file)
@@ -34,8 +34,8 @@
  * Copyright © 2008 Florent Bruneau
  */
 
-#ifndef PFIXTOOLS_RBL_H
-#define PFIXTOOLS_RBL_H
+#ifndef PFIXTOOLS_IPLIST_H
+#define PFIXTOOLS_IPLIST_H
 
 typedef struct rbldb_t rbldb_t;
 
index c2704f1..c350f22 100644 (file)
@@ -37,6 +37,7 @@
 #include "trie.h"
 #include "file.h"
 #include "str.h"
+#include "rbl.h"
 #include "policy_tokens.h"
 
 typedef struct strlist_config_t {
@@ -45,6 +46,10 @@ typedef struct strlist_config_t {
     A(bool)    reverses;
     A(bool)    partiales;
 
+    A(char)     hosts;
+    A(int)      host_offsets;
+    A(int)      host_weights;
+
     int soft_threshold;
     int hard_threshold;
 
@@ -72,6 +77,9 @@ static void strlist_config_delete(strlist_config_t **config)
         array_wipe((*config)->weights);
         array_wipe((*config)->reverses);
         array_wipe((*config)->partiales);
+        array_wipe((*config)->hosts);
+        array_wipe((*config)->host_offsets);
+        array_wipe((*config)->host_weights);
         p_delete(config);
     }
 }
@@ -387,6 +395,39 @@ static bool strlist_filter_constructor(filter_t *filter)
             }
           } break;
 
+          /* dns parameter.
+           *  weight:hostname.
+           * define a RBL to use through DNS resolution.
+           */
+          case ATK_DNS: {
+            int  weight = 0;
+            const char *current = param->value;
+            const char *p = m_strchrnul(param->value, ':');
+            char *next = NULL;
+            for (int i = 0 ; i < 2 ; ++i) {
+                PARSE_CHECK(i == 1 || *p,
+                            "host parameter must contains a weight option");
+                switch (i) {
+                  case 0:
+                    weight = strtol(current, &next, 10);
+                    PARSE_CHECK(next == p && weight >= 0 && weight <= 1024,
+                                "illegal weight value %.*s",
+                                (p - current), current);
+                    break;
+
+                  case 1:
+                    array_add(config->host_offsets, array_len(config->hosts));
+                    array_append(config->hosts, current, strlen(current) + 1);
+                    array_add(config->host_weights, weight);
+                    break;
+                }
+                if (i != 1) {
+                    current = p + 1;
+                    p = m_strchrnul(current, ':');
+                }
+            }
+          } break;
+
           /* hard_threshold parameter is an integer.
            *  If the matching score is greater or equal than this threshold,
            *  the hook "hard_match" is called.
@@ -446,7 +487,7 @@ static bool strlist_filter_constructor(filter_t *filter)
 
     PARSE_CHECK(config->is_email != config->is_hostname,
                 "matched field MUST be emails XOR hostnames");
-    PARSE_CHECK(config->tries.len,
+    PARSE_CHECK(config->tries.len || config->host_offsets.len,
                 "no file parameter in the filter %s", filter->name);
     filter->data = config;
     return true;
@@ -465,6 +506,9 @@ static filter_result_t strlist_filter(const filter_t *filter, const query_t *que
     char normal[BUFSIZ];
     const strlist_config_t *config = filter->data;
     int sum = 0;
+    bool error = true;
+
+
     if (config->is_email && 
         ((config->match_sender && query->state < SMTP_MAIL)
         || (config->match_recipient && query->state != SMTP_RCPT))) {
@@ -492,19 +536,58 @@ static filter_result_t strlist_filter(const filter_t *filter, const query_t *que
                     return HTK_HARD_MATCH;                                     \
                 }                                                              \
             }                                                                  \
+            error = false;                                                     \
+        }                                                                      \
+    }
+#define DNS(Flag, Field)                                                       \
+    if (config->match_ ## Flag) {                                              \
+        const int len = m_strlen(query->Field);                                \
+        strlist_copy(normal, query->Field, len, false);                        \
+        for (uint32_t i = 0 ; len > 0 && i < config->host_offsets.len ; ++i) { \
+            const char *rbl    = array_ptr(config->hosts,                      \
+                                           array_elt(config->host_offsets, i));\
+            const int weight   = array_elt(config->host_weights, i);           \
+            switch (rhbl_check(normal, rbl)) {                                 \
+              case RBL_FOUND:                                                  \
+                error = false;                                                 \
+                sum += weight;                                                 \
+                if (sum >= config->hard_threshold) {                           \
+                    return HTK_HARD_MATCH;                                     \
+                }                                                              \
+                break;                                                         \
+              case RBL_NOTFOUND:                                               \
+                error = false;                                                 \
+                break;                                                         \
+              case RBL_ERROR:                                                  \
+                warn("rbl %s unavailable", rbl);                               \
+                break;                                                         \
+            }                                                                  \
         }                                                                      \
     }
+
     if (config->is_email) {
         LOOKUP(sender, sender);
         LOOKUP(recipient, recipient);
+        DNS(sender, sender);
+        DNS(recipient, recipient);
     } else if (config->is_hostname) {
         LOOKUP(helo, helo_name);
         LOOKUP(client, client_name);
         LOOKUP(reverse, reverse_client_name);
         LOOKUP(recipient, recipient_domain);
         LOOKUP(sender, sender_domain);
+        DNS(helo, helo_name);
+        DNS(client, client_name);
+        DNS(reverse, reverse_client_name);
+        DNS(recipient, recipient_domain);
+        DNS(sender, sender_domain);
     }
+#undef  DNS
 #undef  LOOKUP
+    if (error) {
+        err("filter %s: all the rbls returned an error", filter->name);
+        return HTK_ERROR;
+    }
     if (sum >= config->hard_threshold) {
         return HTK_HARD_MATCH;
     } else if (sum >= config->soft_threshold) {
@@ -530,6 +613,7 @@ static int strlist_init(void)
      */
     (void)filter_param_register(type, "file");
     (void)filter_param_register(type, "rbldns");
+    (void)filter_param_register(type, "dns");
     (void)filter_param_register(type, "hard_threshold");
     (void)filter_param_register(type, "soft_threshold");
     (void)filter_param_register(type, "fields");
index 7e567b1..9cafae1 100644 (file)
@@ -38,7 +38,7 @@
             __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 
 #include "common.h"
-#include "rbl.h"
+#include "iplist.h"
 #include "array.h"
 
 int main(int argc, char *argv[])