X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Finboxrules%2Fserv_inboxrules.c;h=a3e0420307a65929d87f918d7bd31768128f9e6a;hb=f338fe3df988f4c9384e817237526325a31b6971;hp=a54007f8a4b2e7488a616ab7c051f34cbd3d8d1c;hpb=b6845a37c842070f70b8fbb98fb49c5e2cfc9644;p=citadel.git diff --git a/citadel/modules/inboxrules/serv_inboxrules.c b/citadel/modules/inboxrules/serv_inboxrules.c index a54007f8a..a3e042030 100644 --- a/citadel/modules/inboxrules/serv_inboxrules.c +++ b/citadel/modules/inboxrules/serv_inboxrules.c @@ -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,120 +724,541 @@ 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" +}; + +// 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; tcompared_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. +} + + +/* + * 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 (CtdlGetRoom(&CC->room, roomname) != 0) { - syslog(LOG_ERR, "ERROR: cannot load <%s>", roomname); + if (ii->num_rules <= 0) { + syslog(LOG_DEBUG, "inboxrules: rule set is empty"); return; } - /* Initialize the Sieve parser */ + for (i=0; inum_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: + + // FIXME we actually need the rfc822 address + syslog(LOG_DEBUG, "eAuthor is <%s>", msg->cm_fields[eAuthor]); + + 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 + break; + case field_all: // This rule always triggers + rule_activated = 1; + 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; + TRACE; + 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 + } + + TRACE; + 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; @@ -928,10 +1323,13 @@ 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) { @@ -939,7 +1337,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; @@ -990,7 +1388,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];