]> code.citadel.org Git - citadel.git/blobdiff - citadel/modules/inboxrules/serv_inboxrules.c
Removed support for 'before read hooks' because there is no such thing.
[citadel.git] / citadel / modules / inboxrules / serv_inboxrules.c
index 30feea856624410a99285e52999396070bf0acc4..fa9cca82acd06320d5d0c7aafafccc061b9b6e1c 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);
@@ -424,11 +424,10 @@ 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;
@@ -450,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;
 
        /*
@@ -628,33 +627,8 @@ void parse_sieve_config(char *conf, struct sdm_userdata *u) {
        }
 }
 
-/*
- * We found the Sieve configuration for this user.
- * Now do something with it.
- */
-void get_sieve_config_backend(long msgnum, void *userdata) {
-       struct sdm_userdata *u = (struct sdm_userdata *) userdata;
-       struct CtdlMessage *msg;
-       char *conf;
-       long conflen;
-
-       u->config_msgnum = msgnum;
-       msg = CtdlFetchMessage(msgnum, 1, 1);
-       if (msg == NULL) {
-               u->config_msgnum = (-1) ;
-               return;
-       }
-
-       CM_GetAsField(msg, eMesageText, &conf, &conflen);
-
-       CM_Free(msg);
 
-       if (conf != NULL) {
-               parse_sieve_config(conf, u);
-               free(conf);
-       }
 
-}
 
 
 /* 
@@ -750,102 +724,404 @@ sieve2_callback_t ctdl_sieve_callbacks[] = {
 };
 
 
+
+
+
+
+
+
+
+
+
+#endif
+
+
 /*
- * Perform sieve processing for a single room
+ * The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
  */
-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);
+// Fields to be compared
+enum {
+       field_from,             
+       field_tocc,             
+       field_subject,  
+       field_replyto,  
+       field_sender,   
+       field_resentfrom,       
+       field_resentto, 
+       field_envfrom,  
+       field_envto,    
+       field_xmailer,  
+       field_xspamflag,        
+       field_xspamstatus,      
+       field_listid,   
+       field_size,             
+       field_all
+};
+char *field_keys[] = {
+       "from",
+       "tocc",
+       "subject",
+       "replyto",
+       "sender",
+       "resentfrom",
+       "resentto",
+       "envfrom",
+       "envto",
+       "xmailer",
+       "xspamflag",
+       "xspamstatus",
+       "listid",
+       "size",
+       "all"
+};
 
-       /* 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;
-       }
+// Field comparison operators
+enum {
+       fcomp_contains,
+       fcomp_notcontains,
+       fcomp_is,
+       fcomp_isnot,
+       fcomp_matches,
+       fcomp_notmatches
+};
+char *fcomp_keys[] = {
+       "contains",
+       "notcontains",
+       "is",
+       "isnot",
+       "matches",
+       "notmatches"
+};
 
-       /*
-        * 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 );
+// Actions
+enum {
+       action_keep,
+       action_discard,
+       action_reject,
+       action_fileinto,
+       action_redirect,
+       action_vacation
+};
+char *action_keys[] = {
+       "keep",
+       "discard",
+       "reject",
+       "fileinto",
+       "redirect",
+       "vacation"
+};
 
-       if (u.config_msgnum < 0) {
-               syslog(LOG_DEBUG, "No Sieve rules exist.  No processing is required.");
-               return;
+// Size comparison operators
+enum {
+       scomp_larger,
+       scomp_smaller
+};
+char *scomp_keys[] = {
+       "larger",
+       "smaller"
+};
+
+// Final actions
+enum {
+       final_continue,
+       final_stop
+};
+char *final_keys[] = {
+       "continue",
+       "stop"
+};
+
+// This data structure represents ONE inbox rule within the configuration.
+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;
+};
+
+// This data structure represents the entire inbox rules configuration AND current state for a single user.
+struct inboxrules {
+       long lastproc;
+       int num_rules;
+       struct irule *rules;
+};
+
+
+// Destructor for 'struct inboxrules'
+void free_inbox_rules(struct inboxrules *ibr) {
+       free(ibr->rules);
+       free(ibr);
+}
+
+
+// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
+struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
+       int i;
+
+       if (!serialized_rules) {
+               return NULL;
        }
 
-       /*
-        * 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;
+       /* Make a copy of the supplied buffer because we're going to shit all over it with strtok_r() */
+       char *sr = strdup(serialized_rules);
+       if (!sr) {
+               return NULL;
        }
 
-       syslog(LOG_DEBUG, "Rules found.  Performing Sieve processing for <%s>", roomname);
+       struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
+       if (ibr == NULL) {
+               return NULL;
+       }
+       memset(ibr, 0, sizeof(struct inboxrules));
+
+       char *token; 
+       char *rest = sr;
+       while ((token = strtok_r(rest, "\n", &rest))) {
+
+               // 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 become the real rules.
+               if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+                       strcpy(token, "rule|"); 
+                       strcpy(&token[5], &token[14]);
+               }
+
+               // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
+               if (!strncasecmp(token, "rule|", 5)) {
+                       remove_token(&token[5], 0, '|');
+                       char *decoded_rule = malloc(strlen(token));
+                       CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
+                       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
+                       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);
+                               switch(t) {
+                                       case 1:                                                                 // field to compare
+                                               for (i=0; i<=field_all; ++i) {
+                                                       if (!strcasecmp(rtoken, field_keys[i])) {
+                                                               new_rule->compared_field = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 2:                                                                 // field comparison operation
+                                               for (i=0; i<=fcomp_notmatches; ++i) {
+                                                       if (!strcasecmp(rtoken, fcomp_keys[i])) {
+                                                               new_rule->field_compare_op = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 3:                                                                 // field comparison value
+                                               safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
+                                               break;
+                                       case 4:                                                                 // size comparison operation
+                                               for (i=0; i<=scomp_smaller; ++i) {
+                                                       if (!strcasecmp(rtoken, scomp_keys[i])) {
+                                                               new_rule->size_compare_op = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 5:                                                                 // size comparison value
+                                               new_rule->compared_size = atol(rtoken);
+                                               break;
+                                       case 6:                                                                 // action
+                                               for (i=0; i<=action_vacation; ++i) {
+                                                       if (!strcasecmp(rtoken, action_keys[i])) {
+                                                               new_rule->action = i;
+                                                       }
+                                               }
+                                               break;
+                                       case 7:                                                                 // file into (target room)
+                                               safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
+                                               break;
+                                       case 8:                                                                 // redirect to (target address)
+                                               safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
+                                               break;
+                                       case 9:                                                                 // autoreply message
+                                               safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
+                                               break;
+                                       case 10:                                                                // final_action;
+                                               for (i=0; i<=final_stop; ++i) {
+                                                       if (!strcasecmp(rtoken, final_keys[i])) {
+                                                               new_rule->final_action = i;
+                                                       }
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+                       free(decoded_rule);
+
+                       // if we re-serialized this now, what would it look like?
+                       //syslog(LOG_DEBUG, "test reserialize: 0|%s|%s|%s|%s|%ld|%s|%s|%s|%s|%s",
+                               //field_keys[new_rule->compared_field],
+                               //fcomp_keys[new_rule->field_compare_op],
+                               //new_rule->compared_value,
+                               //scomp_keys[new_rule->size_compare_op],
+                               //new_rule->compared_size,
+                               //action_keys[new_rule->action],
+                               //new_rule->file_into,
+                               //new_rule->redirect_to,
+                               //new_rule->autoreply_message,
+                               //final_keys[new_rule->final_action]
+                       //);
+                       // delete the above after moving it to a reserialize function
+
+               }
+
+               // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
+               else if (!strncasecmp(token, "lastproc|", 5)) {
+                       ibr->lastproc = atol(&token[9]);
+               }
 
-       if (CtdlGetRoom(&CC->room, roomname) != 0) {
-               syslog(LOG_ERR, "ERROR: cannot load <%s>", roomname);
-               return;
        }
 
-       /* Initialize the Sieve parser */
+       free(sr);               // free our copy of the source buffer that has now been trashed with null bytes...
+       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;
+       syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
+
+       // FIXME    you are here
+
+
+       //msg = CtdlFetchMessage(msgnum, 
+
+}
+
+
+/*
+ * 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) {
+               if (CC->user.msgnum_inboxrules <= 0) {
+                       return;                                         // this user has no inbox rules
+               }
+
+               msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+               if (msg == NULL) {
+                       return;                                         // config msgnum is set but that message does not exist
+               }
        
-       res = sieve2_alloc(&sieve2_context);
-       if (res != SIEVE2_OK) {
-               syslog(LOG_ERR, "sieve2_alloc() returned %d: %s", res, sieve2_errstr(res));
-               return;
-       }
+               ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
+               CM_Free(msg);
+       
+               if (ii == NULL) {
+                       return;                                         // config message exists but body is null
+               }
 
-       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;
-       }
+               TRACE;
+               syslog(LOG_DEBUG, "inboxrules: for %s", CC->user.fullname);
 
-       /* Validate the script */
+               // do something now FIXME actually write this
 
-       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;
+               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);
        }
+}
 
-       /* 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));
+/*
+ * 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;
+
+
+/*
+ * Perform inbox processing for all rooms which require it
+ */
+void perform_inbox_processing(void) {
+       if (num_urip == 0) {
+               return;                                                                                 // no action required
        }
 
-       /* Rewrite the config if we have to (we're not the user right now) */
-       rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
-}
+       for (int i=0; i<num_urip; ++i) {
+               do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
+       }
 
+       free(users_requiring_inbox_processing);
+       users_requiring_inbox_processing = NULL;
+       num_urip = 0;
+       num_urip_alloc = 0;
+}
 
 
+/*
+ * This function is called after a message is saved to a room.
+ * If it's someone's inbox, we have to check for inbox rules
+ */
+int serv_inboxrules_roomhook(struct ctdlroom *room) {
+
+       // Is this someone's inbox?
+       if (!strcasecmp(&room->QRname[11], MAILROOM)) {
+               long usernum = atol(room->QRname);
+               if (usernum > 0) {
+
+                       // first check to see if this user is already on the list
+                       if (num_urip > 0) {
+                               for (int i=0; i<=num_urip; ++i) {
+                                       if (users_requiring_inbox_processing[i] == usernum) {           // already on the list!
+                                               return(0);
+                                       }
+                               }
+                       }
 
-#endif
+                       // make room if we need to
+                       if (num_urip_alloc == 0) {
+                               num_urip_alloc = 100;
+                               users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
+                       }
+                       else if (num_urip >= num_urip_alloc) {
+                               num_urip_alloc += 100;
+                               users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
+                       }
+                       
+                       // now add the user to the list
+                       users_requiring_inbox_processing[num_urip++] = usernum;
+               }
+       }
 
+       // No errors are possible from this function.
+       return(0);
+}
 
 
 
@@ -853,6 +1129,8 @@ BAIL:
  * 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) {
 
@@ -860,7 +1138,7 @@ 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)) {
                        char *token; 
@@ -911,7 +1189,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];
@@ -946,11 +1224,10 @@ CTDL_MODULE_INIT(sieve)
 {
        if (!threading)
        {
-               // ctdl_sieve_init();
                CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
                CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
-               // CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE, PRIO_HOUSE + 10);
-               // CtdlRegisterCleanupHook(cleanup_sieve);
+               CtdlRegisterRoomHook(serv_inboxrules_roomhook);
+               CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
        }
        
         /* return our module name for the log */