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);
}
-
/*
* 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;
/*
* 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;
/*
}
}
-/*
- * 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);
- }
-
-}
/*
};
+
+
+
+
+
+
+
+
+
+#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"
+};
+
+// Actions
+enum {
+ action_keep,
+ action_discard,
+ action_reject,
+ action_fileinto,
+ action_redirect,
+ action_vacation
+};
+char *action_keys[] = {
+ "keep",
+ "discard",
+ "reject",
+ "fileinto",
+ "redirect",
+ "vacation"
+};
+
+// 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 compared_field;
+ int field_compare_op;
+ 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;
}
- /*
- * 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 );
+ /* 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;
+ }
- if (u.config_msgnum < 0) {
- syslog(LOG_DEBUG, "No Sieve rules exist. No processing is required.");
- return;
+ 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]);
+ }
- /*
- * 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);
+ 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.
+}
+
- if (CtdlGetRoom(&CC->room, roomname) != 0) {
- syslog(LOG_ERR, "ERROR: cannot load <%s>", roomname);
+/*
+ * 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;
}
- /* Initialize the Sieve parser */
+ 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");
+ }
+
- 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;
+ if (msg != NULL) {
+ CM_Free(msg);
}
- /* Validate the script */
+//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 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;
- }
+//struct inboxrules {
+ //long lastproc;
+ //int num_rules;
+ //struct irule *rules;
- /* 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));
- }
+ // Fetch the message, including the body, we need all of it to run our rules.
+ //msg = CtdlFetchMessage(msgnum, 0);
+ //CM_Free(msg);
- /* Rewrite the config if we have to (we're not the user right now) */
- rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
}
-#endif
-
-
/*
* 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) {
- syslog(LOG_DEBUG, "NO RULEZ for %s", CC->user.fullname);
return; // this user has no inbox rules
}
- syslog(LOG_DEBUG, "RULEZ for %s", CC->user.fullname);
+
+ msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+ if (msg == NULL) {
+ return; // config msgnum is set but that message does not exist
+ }
+
+ ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+
+ if (ii == NULL) {
+ return; // config message exists but body is null
+ }
+
+ syslog(LOG_DEBUG, "inboxrules: for %s", CC->user.fullname);
+
+ // do something now FIXME actually write this
+
+ 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;
}
+
/*
* 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) {
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;
// 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];