>From 6589757e6f97028c66a20357f30205d58f00a0ff Mon Sep 17 00:00:00 2001
From: Jonas Wielicki <j.wielicki@sotecware.net>
Date: Fri, 25 Nov 2016 11:30:20 +0100
Subject: [PATCH] Trigger learning on change of configurable IMAP Flags

This allows tight integration with Thunderbird and KMail.
---
 src/antispam-plugin.c |   2 +
 src/mailbox.c         | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/mailbox.h         |   1 +
 src/user.c            |  25 ++++++++++--
 src/user.h            |   2 +
 5 files changed, 133 insertions(+), 3 deletions(-)

diff --git a/src/antispam-plugin.c b/src/antispam-plugin.c
index 52694a3..e9cb260 100644
--- a/src/antispam-plugin.c
+++ b/src/antispam-plugin.c
@@ -1,5 +1,6 @@
 #include "lib.h"
 #include "mail-storage-hooks.h"
+#include "notify-plugin.h"
 
 #include "antispam-plugin.h"
 #include "user.h"
@@ -8,6 +9,7 @@
 
 static struct mail_storage_hooks antispam_plugin_hooks = {
     .mail_user_created = antispam_user_created,
+    .mail_allocated = antispam_mail_allocated,
     .mailbox_allocated = antispam_mailbox_allocated
 };
 
diff --git a/src/mailbox.c b/src/mailbox.c
index 5a0a413..3334275 100644
--- a/src/mailbox.c
+++ b/src/mailbox.c
@@ -4,12 +4,17 @@
 #include "mailbox.h"
 #include "backends.h"
 
+#include <stdbool.h>
+
 static MODULE_CONTEXT_DEFINE_INIT(antispam_storage_module,
 	&mail_storage_module_register);
 static MODULE_CONTEXT_DEFINE_INIT(antispam_transaction_module,
 	&mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(antispam_mail_module,
+	&mail_module_register);
 
 #define TRANSACTION_CONTEXT(obj) MODULE_CONTEXT(obj, antispam_transaction_module)
+#define MAIL_CONTEXT(obj) MODULE_CONTEXT(obj, antispam_mail_module)
 
 struct antispam_transaction
 {
@@ -25,6 +30,35 @@ enum mailbox_copy_type
     MCT_DENY
 };
 
+
+static bool in_flags(const char *kwd, char *const *flags)
+{
+    char *const *curr_flag = flags;
+    for (; *curr_flag; ++curr_flag) {
+        if (strcmp(kwd, *curr_flag) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+static void find_relevant_flags(struct antispam_user *asu,
+                                const char *const *kwds,
+                                bool *has_spam)
+{
+    const char *const *curr_kwd = kwds;
+    for (; *curr_kwd; ++curr_kwd) {
+        *has_spam = *has_spam || in_flags(*curr_kwd, asu->flags_spam);
+        if (*has_spam) {
+            // found, no need to continue
+            break;
+        }
+    }
+}
+
+
+
 static enum mailbox_class antispam_mailbox_classify(struct mailbox *box)
 {
     const char *name = mailbox_get_name(box);
@@ -234,6 +268,78 @@ static void antispam_transaction_rollback(struct mailbox_transaction_context
     i_free(ast);
 }
 
+static void antispam_mail_update_keywords(
+        struct mail *_mail, enum modify_type modify_type,
+        struct mail_keywords *keywords)
+{
+    struct mail_private *mail = (struct mail_private *)_mail;
+	union mail_module_context *lmail = MAIL_CONTEXT(mail);
+    struct antispam_user *asu = USER_CONTEXT(_mail->box->storage->user);
+    struct antispam_transaction *ast = TRANSACTION_CONTEXT(_mail->transaction);
+
+    const char *const *old_keywords = NULL;
+    const char *const *new_keywords = NULL;
+
+    old_keywords = mail_get_keywords(_mail);
+    lmail->super.update_keywords(_mail, modify_type, keywords);
+    new_keywords = mail_get_keywords(_mail);
+
+    bool old_has_spam = false;
+    bool new_has_spam = false;
+
+    find_relevant_flags(asu, old_keywords,
+                        &old_has_spam);
+    find_relevant_flags(asu, mail_get_keywords(_mail),
+                        &new_has_spam);
+
+    const bool learn_as_ham = old_has_spam && !new_has_spam;
+    const bool learn_as_spam = !old_has_spam && new_has_spam;
+
+    i_debug("antispam: keywords changed: old_spam = %d, new_spam = %d\n",
+            old_has_spam, new_has_spam);
+
+    if (learn_as_ham && learn_as_spam) {
+        // wat.
+        i_debug("antispam: wat. both learn as ham and as spam? no way.");
+        return;
+    }
+
+    if (learn_as_spam) {
+        i_debug("antispam: learning as spam");
+        asu->backend->handle_mail(
+                    _mail->transaction,
+                    ast->data,
+                    _mail,
+                    true
+                    );
+    }
+
+    if (learn_as_ham) {
+        i_debug("antispam: learning as ham");
+        asu->backend->handle_mail(
+                    _mail->transaction,
+                    ast->data,
+                    _mail,
+                    false
+                    );
+    }
+}
+
+void antispam_mail_allocated(struct mail *_mail)
+{
+    // XXX: I feel bad about that one
+    struct mail_private *mail = (struct mail_private*)_mail;
+    struct mail_vfuncs *v = mail->vlast;
+    union mail_module_context *lmail;
+
+    lmail = p_new(mail->pool, union mail_module_context, 1);
+    lmail->super = *v;
+    mail->vlast = &lmail->super;
+
+    v->update_keywords = antispam_mail_update_keywords;
+    MODULE_CONTEXT_SET_SELF(mail, antispam_mail_module, lmail);
+}
+
 void antispam_mailbox_allocated(struct mailbox *box)
 {
     struct antispam_mailbox *asmb;
diff --git a/src/mailbox.h b/src/mailbox.h
index a2c13e6..7800487 100644
--- a/src/mailbox.h
+++ b/src/mailbox.h
@@ -23,5 +23,6 @@ struct antispam_mailbox
 };
 
 void antispam_mailbox_allocated(struct mailbox *box);
+void antispam_mail_allocated(struct mail *_mail);
 
 #endif
diff --git a/src/user.c b/src/user.c
index dc90e20..276fa77 100644
--- a/src/user.c
+++ b/src/user.c
@@ -25,6 +25,22 @@ static void parse_folders(struct mail_user *user, const char *infix,
     T_END;
 }
 
+static void parse_flags(struct mail_user *user, const char *suffix,
+                        char ***result)
+{
+    const char *tmp;
+
+    T_BEGIN
+    {
+        tmp = config(user, suffix);
+        if (tmp)
+        {
+            *result = p_strsplit(user->pool, tmp, ";");
+        }
+    }
+    T_END;
+}
+
 static bool check_folders(char ***folders)
 {
     int i;
@@ -86,11 +102,14 @@ void antispam_user_created(struct mail_user *user)
     parse_folders(user, "trash", asu->folders_trash);
     parse_folders(user, "unsure", asu->folders_unsure);
 
+    asu->flags_spam = NULL;
+    parse_flags(user, "spam_flags", &asu->flags_spam);
+
     if (!(check_folders(asu->folders_spam) || check_folders(asu->folders_trash)
-	    || check_folders(asu->folders_unsure)))
+	    || check_folders(asu->folders_unsure) || asu->flags_spam))
     {
-	i_error("antispam plugin folders are not configured for this user");
-	goto bailout;
+        i_error("antispam plugin folders and flags are not configured for this user");
+        goto bailout;
     }
 
     MODULE_CONTEXT_SET(user, antispam_user_module, asu);
diff --git a/src/user.h b/src/user.h
index d643d06..663cd60 100644
--- a/src/user.h
+++ b/src/user.h
@@ -61,6 +61,8 @@ struct antispam_user
     char **folders_trash[NUM_MT];
     char **folders_unsure[NUM_MT];
 
+    char **flags_spam;
+
     // backend config vars pointer
     struct antispam_backend *backend;
     void *backend_config;
-- 
2.10.2

