Fix build on 64bits.
[apps/pfixtools.git] / postlicyd / greylist.c
index a2b3346..6bc65db 100644 (file)
 
 #include "common.h"
 #include "str.h"
+#include "resources.h"
 
+static const static_str_t static_cleanup = { "@@cleanup@@", 11 };
 
 typedef struct greylist_config_t {
     unsigned lookup_by_host : 1;
+    unsigned no_sender      : 1;
+    unsigned no_recipient   : 1;
     int delay;
     int retry_window;
     int client_awl;
     int max_age;
+    int cleanup_period;
 
-    TCBDB *awl_db;
-    TCBDB *obj_db;
+    char  *awlfilename;
+    TCBDB **awl_db;
+    char  *objfilename;
+    TCBDB **obj_db;
 } greylist_config_t;
 
 #define GREYLIST_INIT { .lookup_by_host = false,       \
+                        .no_sender = false,            \
+                        .no_recipient = false,         \
                         .delay = 300,                  \
                         .retry_window = 2 * 24 * 3600, \
                         .client_awl = 5,               \
                         .max_age = 35 * 3600,          \
+                        .cleanup_period = 86400,       \
+                        .awlfilename = NULL,           \
                         .awl_db = NULL,                \
+                        .objfilename = NULL,           \
                         .obj_db = NULL }
 
 struct awl_entry {
@@ -68,6 +80,20 @@ struct obj_entry {
     time_t last;
 };
 
+typedef struct greylist_resource_t {
+    TCBDB *db;
+} greylist_resource_t;
+
+
+static void greylist_resource_wipe(greylist_resource_t *res)
+{
+    if (res->db) {
+        tcbdbsync(res->db);
+        tcbdbdel(res->db);
+    }
+    p_delete(&res);
+}
+
 static inline bool greylist_check_awlentry(const greylist_config_t *config,
                                            struct awl_entry *aent, time_t now)
 {
@@ -84,16 +110,59 @@ static inline bool greylist_check_object(const greylist_config_t *config,
 
 typedef bool (*db_entry_checker_t)(const greylist_config_t *, const void *, time_t);
 
-static TCBDB *greylist_db_get(const greylist_config_t *config,
-                              const char *path, bool cleanup,
+static inline bool greylist_db_need_cleanup(const greylist_config_t *config, TCBDB *db)
+{
+    int len = 0;
+    time_t now = time(NULL);
+    const time_t *last_cleanup = tcbdbget3(db, static_cleanup.str, static_cleanup.len, &len);
+    if (last_cleanup == NULL) {
+        debug("No last cleanup time");
+    } else {
+        debug("Last cleanup time %u, (ie %us ago)",
+              (uint32_t)*last_cleanup, (uint32_t)(now - *last_cleanup));
+    }
+    return last_cleanup == NULL
+        || len != sizeof(*last_cleanup)
+        || (now - *last_cleanup) >= config->cleanup_period;
+}
+
+static TCBDB **greylist_db_get(const greylist_config_t *config, const char *path,
                               size_t entry_len, db_entry_checker_t check)
 {
     TCBDB *awl_db, *tmp_db;
     time_t now = time(NULL);
 
+    greylist_resource_t *res = resource_get("greylist", path);
+    if (res == NULL) {
+        res = p_new(greylist_resource_t, 1);
+        resource_set("greylist", path, res, (resource_destructor_t)greylist_resource_wipe);
+    }
+
+    /* Open the database and check if cleanup is needed
+     */
+    awl_db = res->db;
+    res->db = NULL;
+    if (awl_db == NULL) {
+        awl_db = tcbdbnew();
+        if (!tcbdbopen(awl_db, path, BDBOWRITER | BDBOCREAT)) {
+            err("can not open database: %s", tcbdberrmsg(tcbdbecode(awl_db)));
+            tcbdbdel(awl_db);
+            resource_release("greylist", path);
+            return NULL;
+        }
+    }
+    if (!greylist_db_need_cleanup(config, awl_db) || config->max_age <= 0) {
+        info("%s loaded: no cleanup needed", path);
+        res->db = awl_db;
+        return &res->db;
+    } else {
+        tcbdbsync(awl_db);
+        tcbdbdel(awl_db);
+    }
+
     /* Rebuild a new database after removing too old entries.
      */
-    if (cleanup && config->max_age > 0) {
+    if (config->max_age > 0) {
         uint32_t old_count = 0;
         uint32_t new_count = 0;
         bool replace = false;
@@ -101,7 +170,6 @@ static TCBDB *greylist_db_get(const greylist_config_t *config,
         char tmppath[PATH_MAX];
         snprintf(tmppath, PATH_MAX, "%s.tmp", path);
 
-        info("database cleanup started");
         awl_db = tcbdbnew();
         if (tcbdbopen(awl_db, path, BDBOREADER)) {
             tmp_db = tcbdbnew();
@@ -126,6 +194,7 @@ static TCBDB *greylist_db_get(const greylist_config_t *config,
                         }
                         ++old_count;
                     } while (tcbdbcurnext(cur));
+                    tcbdbput(tmp_db, static_cleanup.str, static_cleanup.len, &now, sizeof(now));
                 }
                 tcxstrdel(key);
                 tcxstrdel(value);
@@ -146,30 +215,38 @@ static TCBDB *greylist_db_get(const greylist_config_t *config,
         /** Cleanup successful, replace the old database with the new one.
          */
         if (trashable) {
-            info("database cleanup finished: database was corrupted, create a new one");
+            info("%s cleanup: database was corrupted, create a new one", path);
             unlink(path);
         } else if (replace) {
-            info("database cleanup finished: before %u entries, after %d entries",
-                   old_count, new_count);
+            info("%s cleanup: done in %us, before %u, after %u entries",
+                 path, (uint32_t)(time(0) - now), old_count, new_count);
             unlink(path);
             if (rename(tmppath, path) != 0) {
                 UNIXERR("rename");
+                resource_release("greylist", path);
                 return NULL;
             }
         } else {
-            info("database cleanup finished: nothing to do, %u entries", new_count);
+            unlink(tmppath);
+            info("%s cleanup: done in %us, nothing to do, %u entries",
+                 path, (uint32_t)(time(0) - now), old_count);
         }
     }
 
     /* Effectively open the database.
      */
+    res->db = NULL;
     awl_db = tcbdbnew();
     if (!tcbdbopen(awl_db, path, BDBOWRITER | BDBOCREAT)) {
         err("can not open database: %s", tcbdberrmsg(tcbdbecode(awl_db)));
         tcbdbdel(awl_db);
+        resource_release("greylist", path);
         return NULL;
     }
-    return awl_db;
+
+    info("%s loaded", path);
+    res->db = awl_db;
+    return &res->db;
 }
 
 
@@ -180,42 +257,40 @@ static bool greylist_initialize(greylist_config_t *config,
 
     if (config->client_awl) {
         snprintf(path, sizeof(path), "%s/%swhitelist.db", directory, prefix);
-        info("loading auto-whitelist database");
-        config->awl_db = greylist_db_get(config, path, true,
+        config->awl_db = greylist_db_get(config, path,
                                          sizeof(struct awl_entry),
                                          (db_entry_checker_t)(greylist_check_awlentry));
         if (config->awl_db == NULL) {
             return false;
         }
+        config->awlfilename = m_strdup(path);
     }
 
     snprintf(path, sizeof(path), "%s/%sgreylist.db", directory, prefix);
-    info("loading greylist database");
-    config->obj_db = greylist_db_get(config, path, true,
+    config->obj_db = greylist_db_get(config, path,
                                      sizeof(struct obj_entry),
                                      (db_entry_checker_t)(greylist_check_object));
     if (config->obj_db == NULL) {
-        if (config->awl_db) {
-            tcbdbdel(config->awl_db);
-            config->awl_db = NULL;
+        if (config->awlfilename) {
+            resource_release("greylist", config->awlfilename);
+            p_delete(&config->awlfilename);
         }
         return false;
     }
+    config->objfilename = m_strdup(path);
 
     return true;
 }
 
 static void greylist_shutdown(greylist_config_t *config)
 {
-    if (config->awl_db) {
-        tcbdbsync(config->awl_db);
-        tcbdbdel(config->awl_db);
-        config->awl_db = NULL;
+    if (config->awlfilename) {
+        resource_release("greylist", config->awlfilename);
+        p_delete(&config->awlfilename);
     }
-    if (config->obj_db) {
-        tcbdbsync(config->obj_db);
-        tcbdbdel(config->obj_db);
-        config->obj_db = NULL;
+    if (config->objfilename) {
+        resource_release("greylist", config->objfilename);
+        p_delete(&config->objfilename);
     }
 }
 
@@ -290,16 +365,15 @@ static const char *c_net(const greylist_config_t *config,
 
 
 static bool try_greylist(const greylist_config_t *config,
-                         const char *sender, const char *c_addr,
-                         const char *c_name, const char *rcpt)
+                         const static_str_t *sender, const static_str_t *c_addr,
+                         const static_str_t *c_name, const static_str_t *rcpt)
 {
 #define INCR_AWL                                              \
     aent.count++;                                             \
     aent.last = now;                                          \
     debug("whitelist entry for %.*s updated, count %d",       \
-          c_addrlen, c_addr, aent.count);                     \
-    tcbdbput(config->awl_db, c_addr, c_addrlen, &aent,        \
-             sizeof(aent));
+          (int)c_addr->len, c_addr->str, aent.count);         \
+    tcbdbput(awl_db, c_addr->str, c_addr->len, &aent, sizeof(aent));
 
     char sbuf[BUFSIZ], cnet[64], key[BUFSIZ];
     const void *res;
@@ -308,29 +382,31 @@ static bool try_greylist(const greylist_config_t *config,
     struct obj_entry oent = { now, now };
     struct awl_entry aent = { 0, 0 };
 
-    int len, klen, c_addrlen = strlen(c_addr);
+    int len, klen;
+    TCBDB * const awl_db = config->awl_db ? *(config->awl_db) : NULL;
+    TCBDB * const obj_db = config->obj_db ? *(config->obj_db) : NULL;
 
     /* Auto whitelist clients.
      */
     if (config->client_awl) {
-        res = tcbdbget3(config->awl_db, c_addr, c_addrlen, &len);
+        res = tcbdbget3(awl_db, c_addr->str, c_addr->len, &len);
         if (res && len == sizeof(aent)) {
             memcpy(&aent, res, len);
             debug("client %.*s has a whitelist entry, count is %d",
-                  c_addrlen, c_addr, aent.count);
+                  (int)c_addr->len, c_addr->str, aent.count);
         }
 
         if (!greylist_check_awlentry(config, &aent, now)) {
             aent.count = 0;
             aent.last  = 0;
             debug("client %.*s whitelist entry too old",
-                  c_addrlen, c_addr);
+                  (int)c_addr->len, c_addr->str);
         }
 
         /* Whitelist if count is enough.
          */
         if (aent.count >= config->client_awl) {
-            debug("client %.*s whitelisted", c_addrlen, c_addr);
+            debug("client %.*s whitelisted", (int)c_addr->len, c_addr->str);
             if (now < aent.last + 3600) {
                 INCR_AWL
             }
@@ -344,11 +420,12 @@ static bool try_greylist(const greylist_config_t *config,
     /* Lookup.
      */
     klen = snprintf(key, sizeof(key), "%s/%s/%s",
-                    c_net(config, c_addr, c_name, cnet, sizeof(cnet)),
-                    sender_normalize(sender, sbuf, sizeof(sbuf)), rcpt);
+                    c_net(config, c_addr->str, c_name->str, cnet, sizeof(cnet)),
+                    config->no_sender ? "" : sender_normalize(sender->str, sbuf, sizeof(sbuf)),
+                    config->no_recipient ? "" : rcpt->str);
     klen = MIN(klen, ssizeof(key) - 1);
 
-    res = tcbdbget3(config->obj_db, key, klen, &len);
+    res = tcbdbget3(obj_db, key, klen, &len);
     if (res && len == sizeof(oent)) {
         memcpy(&oent, res, len);
         debug("found a greylist entry for %.*s", klen, key);
@@ -369,7 +446,7 @@ static bool try_greylist(const greylist_config_t *config,
     /* Update.
      */
     oent.last = now;
-    tcbdbput(config->obj_db, key, klen, &oent, sizeof(oent));
+    tcbdbput(obj_db, key, klen, &oent, sizeof(oent));
 
     /* Auto whitelist clients:
      *  algorithm:
@@ -430,13 +507,16 @@ static bool greylist_filter_constructor(filter_t *filter)
 
     foreach (filter_param_t *param, filter->params) {
         switch (param->type) {
-          FILTER_PARAM_PARSE_STRING(PATH,   path);
-          FILTER_PARAM_PARSE_STRING(PREFIX, prefix);
+          FILTER_PARAM_PARSE_STRING(PATH,   path, false);
+          FILTER_PARAM_PARSE_STRING(PREFIX, prefix, false);
           FILTER_PARAM_PARSE_BOOLEAN(LOOKUP_BY_HOST, config->lookup_by_host);
+          FILTER_PARAM_PARSE_BOOLEAN(NO_SENDER, config->no_sender);
+          FILTER_PARAM_PARSE_BOOLEAN(NO_RECIPIENT, config->no_recipient);
           FILTER_PARAM_PARSE_INT(RETRY_WINDOW, config->retry_window);
           FILTER_PARAM_PARSE_INT(CLIENT_AWL,   config->client_awl);
           FILTER_PARAM_PARSE_INT(DELAY,        config->delay);
           FILTER_PARAM_PARSE_INT(MAX_AGE,      config->max_age);
+          FILTER_PARAM_PARSE_INT(CLEANUP_PERIOD, config->cleanup_period);
 
           default: break;
         }
@@ -458,16 +538,21 @@ static void greylist_filter_destructor(filter_t *filter)
 }
 
 static filter_result_t greylist_filter(const filter_t *filter,
-                                       const query_t *query)
+                                       const query_t *query,
+                                       filter_context_t *context)
 {
     const greylist_config_t *config = filter->data;
-    if (query->state != SMTP_RCPT) {
-        warn("greylisting only works as smtpd_recipient_restrictions");
+    if (!config->no_recipient && query->state != SMTP_RCPT) {
+        warn("greylisting on recipient only works as smtpd_recipient_restrictions");
+        return HTK_ABORT;
+    }
+    if (!config->no_sender && query->state < SMTP_MAIL) {
+        warn("greylisting on sender must be performed after (or at) MAIL TO");
         return HTK_ABORT;
     }
 
-    return try_greylist(config, query->sender, query->client_address,
-                        query->client_name, query->recipient) ?
+    return try_greylist(config, &query->sender, &query->client_address,
+                        &query->client_name, &query->recipient) ?
            HTK_WHITELIST : HTK_GREYLIST;
 }
 
@@ -475,7 +560,7 @@ static int greylist_init(void)
 {
     filter_type_t type =  filter_register("greylist", greylist_filter_constructor,
                                           greylist_filter_destructor,
-                                          greylist_filter);
+                                          greylist_filter, NULL, NULL);
     /* Hooks.
      */
     (void)filter_hook_register(type, "abort");
@@ -486,10 +571,13 @@ static int greylist_init(void)
     /* Parameters.
      */
     (void)filter_param_register(type, "lookup_by_host");
+    (void)filter_param_register(type, "no_sender");
+    (void)filter_param_register(type, "no_recipient");
     (void)filter_param_register(type, "delay");
     (void)filter_param_register(type, "retry_window");
     (void)filter_param_register(type, "client_awl");
     (void)filter_param_register(type, "max_age");
+    (void)filter_param_register(type, "cleanup_period");
     (void)filter_param_register(type, "path");
     (void)filter_param_register(type, "prefix");
     return 0;