more inbox filter testing, and yes the build is still broken
[citadel.git] / citadel / modules / inboxrules / serv_inboxrules.c
index 44294ddb9dab1aa103bdbceeddfd3e8363db6927..0f0506680bdbe3009316c0cb08d1a1287f86a7c2 100644 (file)
@@ -75,7 +75,7 @@ int ctdl_redirect(void)
                return SIEVE2_ERROR_BADARGS;
        }
 
-       msg = CtdlFetchMessage(cs->msgnum, 1, 1);
+       msg = CtdlFetchMessage(cs->msgnum, 1);
        if (msg == NULL) {
                syslog(LOG_WARNING, "REDIRECT failed: unable to fetch msg %ld", cs->msgnum);
                free_recipients(valid);
@@ -427,7 +427,7 @@ int ctdl_getheaders(sieve2_context_t *s, void *my) {
 /*
  * Perform sieve processing for one message (called by sieve_do_room() for each message)
  */
-void sieve_do_msg(long msgnum, void *userdata) {
+void inbox_do_msg(long msgnum, void *userdata) {
        struct sdm_userdata *u = (struct sdm_userdata *) userdata;
        sieve2_context_t *sieve2_context;
        struct ctdl_sieve my;
@@ -449,7 +449,7 @@ void sieve_do_msg(long msgnum, void *userdata) {
        /*
         * Make sure you include message body so you can get those second-level headers ;)
         */
-       msg = CtdlFetchMessage(msgnum, 1, 1);
+       msg = CtdlFetchMessage(msgnum, 1);
        if (msg == NULL) return;
 
        /*
@@ -724,101 +724,23 @@ sieve2_callback_t ctdl_sieve_callbacks[] = {
 };
 
 
-/*
- * Perform sieve processing for a single room
- */
-void sieve_do_room(char *roomname) {
-       
-       struct sdm_userdata u;
-       sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
-       int res;                                        /* Return code from libsieve calls */
-       long orig_lastproc = 0;
 
-       memset(&u, 0, sizeof u);
 
-       /* See if the user who owns this 'mailbox' has any Sieve scripts that
-        * require execution.
-        */
-       snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM);
-       if (CtdlGetRoom(&CC->room, u.config_roomname) != 0) {
-               syslog(LOG_DEBUG, "<%s> does not exist.  No processing is required.", u.config_roomname);
-               return;
-       }
 
-       /*
-        * Find the sieve scripts and control record and do something
-        */
-       u.config_msgnum = (-1);
-       CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, get_sieve_config_backend, (void *)&u );
-
-       if (u.config_msgnum < 0) {
-               syslog(LOG_DEBUG, "No Sieve rules exist.  No processing is required.");
-               return;
-       }
-
-       /*
-        * Check to see whether the script is empty and should not be processed.
-        * A script is considered non-empty if it contains at least one semicolon.
-        */
-       if (
-               (get_active_script(&u) == NULL)
-               || (strchr(get_active_script(&u), ';') == NULL)
-       ) {
-               syslog(LOG_DEBUG, "Sieve script is empty.  No processing is required.");
-               return;
-       }
 
-       syslog(LOG_DEBUG, "Rules found.  Performing Sieve processing for <%s>", roomname);
 
-       if (CtdlGetRoom(&CC->room, roomname) != 0) {
-               syslog(LOG_ERR, "ERROR: cannot load <%s>", roomname);
-               return;
-       }
 
-       /* Initialize the Sieve parser */
-       
-       res = sieve2_alloc(&sieve2_context);
-       if (res != SIEVE2_OK) {
-               syslog(LOG_ERR, "sieve2_alloc() returned %d: %s", res, sieve2_errstr(res));
-               return;
-       }
 
-       res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
-       if (res != SIEVE2_OK) {
-               syslog(LOG_ERR, "sieve2_callbacks() returned %d: %s", res, sieve2_errstr(res));
-               goto BAIL;
-       }
 
-       /* Validate the script */
-
-       struct ctdl_sieve my;           /* dummy ctdl_sieve struct just to pass "u" along */
-       memset(&my, 0, sizeof my);
-       my.u = &u;
-       res = sieve2_validate(sieve2_context, &my);
-       if (res != SIEVE2_OK) {
-               syslog(LOG_ERR, "sieve2_validate() returned %d: %s", res, sieve2_errstr(res));
-               goto BAIL;
-       }
-
-       /* Do something useful */
-       u.sieve2_context = sieve2_context;
-       orig_lastproc = u.lastproc;
-       CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL, sieve_do_msg, (void *) &u);
-
-BAIL:
-       res = sieve2_free(&sieve2_context);
-       if (res != SIEVE2_OK) {
-               syslog(LOG_ERR, "sieve2_free() returned %d: %s", res, sieve2_errstr(res));
-       }
-
-       /* Rewrite the config if we have to (we're not the user right now) */
-       rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
-}
 
 #endif
 
 
+/*
+ * The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
+ */
 
+// Fields to be compared
 enum {
        field_from,             
        field_tocc,             
@@ -854,7 +776,7 @@ char *field_keys[] = {
        "all"
 };
 
-
+// Field comparison operators
 enum {
        fcomp_contains,
        fcomp_notcontains,
@@ -872,6 +794,7 @@ char *fcomp_keys[] = {
        "notmatches"
 };
 
+// Actions
 enum {
        action_keep,
        action_discard,
@@ -889,6 +812,7 @@ char *action_keys[] = {
        "vacation"
 };
 
+// Size comparison operators
 enum {
        scomp_larger,
        scomp_smaller
@@ -898,6 +822,7 @@ char *scomp_keys[] = {
        "smaller"
 };
 
+// Final actions
 enum {
        final_continue,
        final_stop
@@ -907,9 +832,10 @@ char *final_keys[] = {
        "stop"
 };
 
+// This data structure represents ONE inbox rule within the configuration.
 struct irule {
-       int field_compare_op;
        int compared_field;
+       int field_compare_op;
        char compared_value[128];
        int size_compare_op;
        long compared_size;
@@ -920,6 +846,7 @@ struct irule {
        int final_action;
 };
 
+// This data structure represents the entire inbox rules configuration AND current state for a single user.
 struct inboxrules {
        long lastproc;
        int num_rules;
@@ -927,15 +854,14 @@ struct inboxrules {
 };
 
 
+// Destructor for 'struct inboxrules'
 void free_inbox_rules(struct inboxrules *ibr) {
        free(ibr->rules);
        free(ibr);
 }
 
 
-/*
- * Convert the serialized inbox rules message to a data type.
- */
+// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
 struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
        int i;
 
@@ -961,7 +887,7 @@ struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
 
                // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
                // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for later re-editing.
-               // Now, the rules hidden in the comments are the real rules.
+               // Now, the rules hidden in the comments become the real rules.
                if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
                        strcpy(token, "rule|"); 
                        strcpy(&token[5], &token[14]);
@@ -969,26 +895,20 @@ struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
 
                // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
                if (!strncasecmp(token, "rule|", 5)) {
-                       syslog(LOG_DEBUG, "rule: %s", &token[5]);
                        remove_token(&token[5], 0, '|');
                        char *decoded_rule = malloc(strlen(token));
                        CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
-                       TRACE;
-                       syslog(LOG_DEBUG, "%s", decoded_rule);  
-
                        ibr->num_rules++;
                        ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
                        struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
                        memset(new_rule, 0, sizeof(struct irule));
 
                        // We have a rule , now parse it
-                       syslog(LOG_DEBUG, "Detokenizing: %s", decoded_rule);
                        char rtoken[SIZ];
                        int nt = num_tokens(decoded_rule, '|');
                        for (int t=0; t<nt; ++t) {
                                extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
                                striplt(rtoken);
-                               syslog(LOG_DEBUG, "Token %d : %s", t, rtoken);
                                switch(t) {
                                        case 1:                                                                 // field to compare
                                                for (i=0; i<=field_all; ++i) {
@@ -1071,51 +991,271 @@ struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
        }
 
        free(sr);               // free our copy of the source buffer that has now been trashed with null bytes...
-       abort();
        return(ibr);            // and return our complex data type to the caller.
 }
 
 
+/*
+ * Process a single message.  We know the room, the user, the rules, the message number, etc.
+ */
+void inbox_do_msg(long msgnum, void *userdata) {
+       struct inboxrules *ii = (struct inboxrules *) userdata;
+       struct CtdlMessage *msg = NULL;
+       int headers_loaded = 0;
+       int body_loaded = 0;
+       int metadata_loaded = 0;
+       int rule_activated = 0;                 // On each rule, this is set if the compare succeeds and the rule activates.
+       char compare_me[SIZ];                   // On each rule, we will store the field to be compared here.
+       int i;
+
+       syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
+
+       if (ii->num_rules <= 0) {
+               syslog(LOG_DEBUG, "inboxrules: rule set is empty");
+               return;
+       }
+
+       for (i=0; i<ii->num_rules; ++i) {
+               syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
+               rule_activated = 0;
+
+               // Before doing a field compare, check to see if we have the correct parts of the message in memory.
+
+               switch(ii->rules[i].compared_field) {
+                       // These fields require loading only the top-level headers
+                       case field_from:                // From:
+                       case field_tocc:                // To: or Cc:
+                       case field_subject:             // Subject:
+                       case field_replyto:             // Reply-to:
+                       case field_listid:              // List-ID:
+                       case field_envto:               // Envelope-to:
+                       case field_envfrom:             // Return-path:
+                               if (!headers_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
+                                       msg = CtdlFetchMessage(msgnum, 0);
+                                       if (!msg) {
+                                               return;
+                                       }
+                                       headers_loaded = 1;
+                               }
+                               break;
+                       // These fields are not stored as Citadel headers, and therefore require a full message load.
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+                               if (!body_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
+                                       if (msg != NULL) {
+                                               CM_Free(msg);
+                                       }
+                                       msg = CtdlFetchMessage(msgnum, 1);
+                                       if (!msg) {
+                                               return;
+                                       }
+                                       headers_loaded = 1;
+                                       body_loaded = 1;
+                               }
+                               break;
+                       case field_size:
+                               if (!metadata_loaded) {
+                                       syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
+                                       // FIXME do this
+                                       metadata_loaded = 1;
+                               }
+                               break;
+                       case field_all:
+                               syslog(LOG_DEBUG, "this is an always-on rule");
+                               break;
+                       default:
+                               syslog(LOG_DEBUG, "inboxrules: unknown rule key");
+               }
+
+               // If the rule involves a field comparison, load the field to be compared.
+               compare_me[0] = 0;
+               switch(ii->rules[i].compared_field) {
+
+                       case field_from:                // From:
+                               syslog(LOG_DEBUG, "eAuthor is <%s>", msg->cm_fields[erFc822Addr]);
+                               safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
+                               break;
+                       case field_tocc:                // To: or Cc:
+                               if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
+                                       safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
+                               }
+                               if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
+                                       if (!IsEmptyStr(compare_me)) {
+                                               strcat(compare_me, ",");
+                                       }
+                                       safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
+                               }
+                               break;
+                       case field_subject:             // Subject:
+                               safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
+                               break;
+                       case field_replyto:             // Reply-to:
+                               safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
+                               break;
+                       case field_listid:              // List-ID:
+                               safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
+                               break;
+                       case field_envto:               // Envelope-to:
+                               safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
+                               break;
+                       case field_envfrom:             // Return-path:
+                               safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
+                               break;
+
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+
+                       default:
+                               break;
+               }
+
+               // Message data to compare is loaded, now do something.
+               switch(ii->rules[i].compared_field) {
+                       case field_from:                // From:
+                       case field_tocc:                // To: or Cc:
+                       case field_subject:             // Subject:
+                       case field_replyto:             // Reply-to:
+                       case field_listid:              // List-ID:
+                       case field_envto:               // Envelope-to:
+                       case field_envfrom:             // Return-path:
+                       case field_sender:
+                       case field_resentfrom:
+                       case field_resentto:
+                       case field_xmailer:
+                       case field_xspamflag:
+                       case field_xspamstatus:
+
+                               // For all of the above fields, we can compare the field we've loaded into the buffer.
+                               // FIXME you are here YOU ARE HERE
+                               syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
+                               switch(ii->rules[i].field_compare_op) {
+                                       case fcomp_contains:
+                                       case fcomp_matches:
+                                               rule_activated = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
+                                               break;
+                                       case fcomp_notcontains:
+                                       case fcomp_notmatches:
+                                               rule_activated = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 0 : 1);
+                                               break;
+                                       case fcomp_is:
+                                               rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
+                                               break;
+                                       case fcomp_isnot:
+                                               rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 1 : 0);
+                                               break;
+                               }
+
+                       case field_size:
+                               rule_activated = 0;     // FIXME
+                               syslog(LOG_DEBUG, "\033[31m\033[7m RULE NOT ACTIVATED \033[0m");
+                               break;
+                       case field_all:                 // This rule always triggers
+                               rule_activated = 1;
+                               syslog(LOG_DEBUG, "\033[32m\033[7m RULE ACTIVATED \033[0m");
+                               break;
+                       default:
+                               TRACE;
+                               break;
+               }
+
+               if (rule_activated) {
+                       syslog(LOG_DEBUG, "rule activated");
+               }
+               else {
+                       syslog(LOG_DEBUG, "rule NOT activated");
+               }
+
+       
+       }
+
+       if (msg != NULL) {
+               CM_Free(msg);
+       }
+
+//struct irule {
+       //int field_compare_op;
+       //int compared_field;
+       //char compared_value[128];
+       //int size_compare_op;
+       //long compared_size;
+       //int action;
+       //char file_into[ROOMNAMELEN];
+       //char redirect_to[1024];
+       //char autoreply_message[SIZ];
+       //int final_action;
+//};
+
+//struct inboxrules {
+       //long lastproc;
+       //int num_rules;
+       //struct irule *rules;
+
+
+       // Fetch the message, including the body, we need all of it to run our rules.
+       //msg = CtdlFetchMessage(msgnum, 0);
+       //CM_Free(msg);
+
+}
+
+
 /*
  * A user account is identified as requring inbox processing.
  * Do it.
  */
 void do_inbox_processing_for_user(long usernum) {
+       struct CtdlMessage *msg;
+       struct inboxrules *ii;
+       char roomname[ROOMNAMELEN];
+
        if (CtdlGetUserByNumber(&CC->user, usernum) == 0) {
-               TRACE;
                if (CC->user.msgnum_inboxrules <= 0) {
                        return;                                         // this user has no inbox rules
                }
 
-               struct CtdlMessage *msg;
-               char *conf;
-               long conflen;
-       
-               msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1, 1);
+               msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
                if (msg == NULL) {
                        return;                                         // config msgnum is set but that message does not exist
                }
        
-               CM_GetAsField(msg, eMesageText, &conf, &conflen);
+               ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
                CM_Free(msg);
        
-               if (conf == NULL) {
+               if (ii == NULL) {
                        return;                                         // config message exists but body is null
                }
 
-               syslog(LOG_DEBUG, "RULEZ for %s", CC->user.fullname);
-               syslog(LOG_DEBUG, "%s", conf);
+               syslog(LOG_DEBUG, "inboxrules: for %s", CC->user.fullname);
 
                // do something now FIXME actually write this
 
-               free(conf);
+               snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
+               if (CtdlGetRoom(&CC->room, roomname) == 0) {
+                       syslog(LOG_DEBUG, "GOT DA ROOM!  WE R000000000L!");
+
+                               /* Do something useful */
+                               CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
+
+               }
+
+               // FIXME reserialize our inbox rules/state and write changes back to the config room
+               free_inbox_rules(ii);
        }
 }
 
 
 /*
  * Here is an array of users (by number) who have received messages in their inbox and may require processing.
-*/
+ */
 long *users_requiring_inbox_processing = NULL;
 int num_urip = 0;
 int num_urip_alloc = 0;
@@ -1185,6 +1325,8 @@ int serv_inboxrules_roomhook(struct ctdlroom *room) {
  * Get InBox Rules
  *
  * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
+ * 
+ * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
  */
 void cmd_gibr(char *argbuf) {
 
@@ -1192,14 +1334,9 @@ void cmd_gibr(char *argbuf) {
 
        cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
 
-       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1, 1);
+       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
        if (msg != NULL) {
                if (!CM_IsEmpty(msg, eMesageText)) {
-
-
-                       struct inboxrules *ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
-                       free_inbox_rules(ii);
-
                        char *token; 
                        char *rest = msg->cm_fields[eMesageText];
                        while ((token = strtok_r(rest, "\n", &rest))) {
@@ -1248,7 +1385,7 @@ void cmd_pibr(char *argbuf) {
 
        // Fetch the existing config so we can merge in anything that is NOT a rule 
        // (Does not start with "rule|" but has at least one vertical bar)
-       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1, 1);
+       struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
        if (msg != NULL) {
                if (!CM_IsEmpty(msg, eMesageText)) {
                        rest = msg->cm_fields[eMesageText];