Removed an unused parameter from CtdlSubmitMsg(). Why was it even there?
[citadel.git] / citadel / modules / inboxrules / serv_inboxrules.c
index 376406890271360da6b96758c556d3883a611b17..4979e57150e74f64900535fef0bd7de83e26c0bf 100644 (file)
 #include "ctdl_module.h"
 
 
-#if 0
-
-
-
-
-
-
-/*
- * Callback function to indicate that a vacation message should be generated
- */
-int ctdl_vacation(sieve2_context_t *s, void *my)
-{
-       struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
-       struct sdm_vacation *vptr;
-       int days = 1;
-       const char *message;
-       char *vacamsg_text = NULL;
-       char vacamsg_subject[1024];
-
-       syslog(LOG_DEBUG, "Action is VACATION");
-
-       message = sieve2_getvalue_string(s, "message");
-       if (message == NULL) return SIEVE2_ERROR_BADARGS;
-
-       if (sieve2_getvalue_string(s, "subject") != NULL) {
-               safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject);
-       }
-       else {
-               snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject);
-       }
-
-       days = sieve2_getvalue_int(s, "days");
-       if (days < 1) days = 1;
-       if (days > MAX_VACATION) days = MAX_VACATION;
-
-       /* Check to see whether we've already alerted this sender that we're on vacation. */
-       for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
-               if (!strcasecmp(vptr->fromaddr, cs->sender)) {
-                       if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) {
-                               syslog(LOG_DEBUG, "Already alerted <%s> recently.", cs->sender);
-                               return SIEVE2_OK;
-                       }
-               }
-       }
-
-       /* Assemble the reject message. */
-       vacamsg_text = malloc(strlen(message) + 1024);
-       if (vacamsg_text == NULL) {
-               return SIEVE2_ERROR_FAIL;
-       }
-
-       sprintf(vacamsg_text, 
-               "Content-type: text/plain charset=utf-8\n"
-               "\n"
-               "%s\n"
-               "\n"
-       ,
-               message
-       );
-
-       quickie_message(        /* This delivers the message */
-               NULL,
-               cs->envelope_to,
-               cs->sender,
-               NULL,
-               vacamsg_text,
-               FMT_RFC822,
-               vacamsg_subject
-       );
-
-       free(vacamsg_text);
-
-       /* Now update the list to reflect the fact that we've alerted this sender.
-        * If they're already in the list, just update the timestamp.
-        */
-       for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
-               if (!strcasecmp(vptr->fromaddr, cs->sender)) {
-                       vptr->timestamp = time(NULL);
-                       return SIEVE2_OK;
-               }
-       }
-
-       /* If we get to this point, create a new record.
-        */
-       vptr = malloc(sizeof(struct sdm_vacation));
-       memset(vptr, 0, sizeof(struct sdm_vacation));
-       vptr->timestamp = time(NULL);
-       safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr);
-       vptr->next = cs->u->first_vacation;
-       cs->u->first_vacation = vptr;
-
-       return SIEVE2_OK;
-}
-
-
-
-#endif
-
-
 /*
  * The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
  */
@@ -379,6 +280,7 @@ struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
                }
 
                // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
+               // This is a legacy location for this value and will only be used if it's the only one present.
                else if (!strncasecmp(token, "lastproc|", 5)) {
                        ibr->lastproc = atol(&token[9]);
                }
@@ -471,20 +373,18 @@ int inbox_do_redirect(struct irule *rule, long msgnum) {
                return(1);                                      // don't delete the inbox copy if this failed
        }
 
-       CtdlSubmitMsg(msg, valid, NULL, 0);                     // send the message to the new recipient
+       CtdlSubmitMsg(msg, valid, NULL);                        // send the message to the new recipient
        free_recipients(valid);
        CM_Free(msg);
        return(0);                                              // delete the inbox copy
 }
 
 
-// Perform the "reject" action (delete the message, and tell the sender we deleted it)
-//
+/*
+ * Perform the "reject" action (delete the message, and tell the sender we deleted it)
+ */
 void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
-       syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject message: <%s>",
-               msg->cm_fields[erFc822Addr],
-               rule->autoreply_message
-       );
+       syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
 
        // If we can't determine who sent the message, reject silently.
        char *sender;
@@ -517,10 +417,10 @@ void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
 
        // Deliver the message
        quickie_message(
-               NULL,
+               " ",
                msg->cm_fields[eenVelopeTo],
                sender,
-               NULL,
+               MAILROOM,
                reject_text,
                FMT_RFC822,
                "Delivery status notification"
@@ -529,6 +429,66 @@ void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
 }
 
 
+/*
+ * Perform the "vacation" action (send an automatic response)
+ */
+void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
+       syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
+
+       // If we can't determine who sent the message, no auto-reply can be sent.
+       char *sender;
+       if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+               sender = msg->cm_fields[eMessagePath];
+       }
+       else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+               sender = msg->cm_fields[erFc822Addr];
+       }
+       else {
+               return;
+       }
+
+       // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
+       // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
+       StrBuf *u = NewStrBuf();
+       StrBufPrintf(u, "vacation/%x/%x/%x",
+               HashLittle(sender, strlen(sender)),
+               HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
+               HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
+       );
+       int already_seen = CheckIfAlreadySeen(u);
+       FreeStrBuf(&u);
+
+       if (!already_seen) {
+               // Assemble the auto-reply message.
+               StrBuf *reject_text = NewStrBuf();
+               if (reject_text == NULL) {
+                       return;
+               }
+
+               StrBufPrintf(reject_text, 
+                       "Content-type: text/plain\n"
+                       "\n"
+                       "%s\n"
+                       "\n"
+               ,
+                       rule->autoreply_message
+               );
+       
+               // Deliver the auto-reply.
+               quickie_message(
+                       "",
+                       msg->cm_fields[eenVelopeTo],
+                       sender,
+                       MAILROOM,
+                       ChrPtr(reject_text),
+                       FMT_RFC822,
+                       "Delivery status notification"
+               );
+               FreeStrBuf(&reject_text);
+       }
+}
+
+
 /*
  * Process a single message.  We know the room, the user, the rules, the message number, etc.
  */
@@ -541,6 +501,7 @@ void inbox_do_msg(long msgnum, void *userdata) {
        struct MetaData smi;                    // If we are loading the metadata to compare, put it here.
        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 compare_compound = 0;               // Set to 1 when we are comparing both display name and email address
        int keep_message = 1;                   // Nonzero to keep the message in the inbox after processing, 0 to delete it.
        int i;
 
@@ -611,12 +572,22 @@ void inbox_do_msg(long msgnum, void *userdata) {
 
                // If the rule involves a field comparison, load the field to be compared.
                compare_me[0] = 0;
+               compare_compound = 0;
                switch(ii->rules[i].compared_field) {
-
                        case field_from:                // From:
-                               if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+                               if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
+                                       snprintf(compare_me, sizeof compare_me, "%s|%s",
+                                               msg->cm_fields[eAuthor],
+                                               msg->cm_fields[erFc822Addr]
+                                       );
+                                       compare_compound = 1;           // there will be two fields to compare "name|address"
+                               }
+                               else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
                                        safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
                                }
+                               else if (!IsEmptyStr(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])) {
@@ -684,39 +655,49 @@ void inbox_do_msg(long msgnum, void *userdata) {
 
                                // For all of the above fields, we can compare the field we've loaded into the buffer.
                                syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
+                               int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
+                               int exact_match = 0;
+                               if (compare_compound) {
+                                       char *sep = strchr(compare_me, '|');
+                                       if (sep) {
+                                               *sep = 0;
+                                               exact_match =
+                                                       (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
+                                                       + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
+                                               ;
+                                       }
+                               }
+                               else {
+                                       exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
+                               }
+                               syslog(LOG_DEBUG, "substring match: %d", substring_match);
+                               syslog(LOG_DEBUG, "exact match: %d", exact_match);
                                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);
-                                               syslog(LOG_DEBUG, "Does %s contain %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
+                                               rule_activated = substring_match;
                                                break;
                                        case fcomp_notcontains:
                                        case fcomp_notmatches:
-                                               rule_activated = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 0 : 1);
-                                               syslog(LOG_DEBUG, "Does %s contain %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
+                                               rule_activated = !substring_match;
                                                break;
                                        case fcomp_is:
-                                               rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
-                                               syslog(LOG_DEBUG, "Does %s equal %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
+                                               rule_activated = exact_match;
                                                break;
                                        case fcomp_isnot:
-                                               rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 1 : 0);
-                                               syslog(LOG_DEBUG, "Does %s equal %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
+                                               rule_activated = !exact_match;
                                                break;
                                }
                                break;
 
                        case field_size:
                                rule_activated = 0;
-                               syslog(LOG_DEBUG, "comparing actual message size %ld to rule message size %ld", smi.meta_rfc822_length, ii->rules[i].compared_size);
                                switch(ii->rules[i].field_compare_op) {
                                        case scomp_larger:
                                                rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
-                                               syslog(LOG_DEBUG, "Is %ld larger than %ld? %s", smi.meta_rfc822_length, ii->rules[i].compared_size, (smi.meta_rfc822_length > ii->rules[i].compared_size) ? "yes":"no");
                                                break;
                                        case scomp_smaller:
                                                rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
-                                               syslog(LOG_DEBUG, "Is %ld smaller than %ld? %s", smi.meta_rfc822_length, ii->rules[i].compared_size, (smi.meta_rfc822_length < ii->rules[i].compared_size) ? "yes":"no");
                                                break;
                                }
                                break;
@@ -724,7 +705,7 @@ void inbox_do_msg(long msgnum, void *userdata) {
                                rule_activated = 1;
                                break;
                        default:                        // no matches, fall through and do nothing
-                               syslog(LOG_DEBUG, "inboxrules: an unknown field comparison was encountered");
+                               syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
                                rule_activated = 0;
                                break;
                }
@@ -752,7 +733,7 @@ void inbox_do_msg(long msgnum, void *userdata) {
                                        keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
                                        break;
                                case action_vacation:
-                                       // inbox_do_vacation(&ii->rules[i], msg);
+                                       inbox_do_vacation(&ii->rules[i], msg);
                                        keep_message = 1;
                                        break;
                        }
@@ -783,75 +764,6 @@ void inbox_do_msg(long msgnum, void *userdata) {
 }
 
 
-/*
- * Rewrite the rule set to disk after it has been modified
- */
-void rewrite_rules_to_disk(const char *new_config) {
-       long old_msgnum = CC->user.msgnum_inboxrules;
-       char userconfigroomname[ROOMNAMELEN];
-       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
-       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
-       CtdlGetUserLock(&CC->user, CC->curr_user);
-       CC->user.msgnum_inboxrules = new_msgnum;
-       CtdlPutUserLock(&CC->user);
-       if (old_msgnum > 0) {
-               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
-               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
-       }
-}
-
-
-/*
- * After we finish processing, rewrite the config to disk.
- * This saves things like vacation logs and the last-processed message number.
- */
-void inbox_rewrite_rules(struct inboxrules *ii, long usernum) {
-       StrBuf *text;
-       StrBuf *rule;
-       char *base64_encoded_rule;
-
-       text = NewStrBufPlain(NULL, SIZ);
-       rule = NewStrBufPlain(NULL, SIZ);
-
-       StrBufPrintf(text,
-               "Content-type: application/x-citadel-sieve-config\n"
-               "\n"
-               "lastproc|%ld\n"
-               ,
-               ii->lastproc
-       );
-
-       for (int i=0; i<ii->num_rules; ++i) {
-               StrBufPrintf(rule, "%d|%s|%s|%s|%s|%ld|%s|%s|%s|%s|%s|",
-                       i,
-                       field_keys[ii->rules[i].compared_field],
-                       fcomp_keys[ii->rules[i].field_compare_op],
-                       ii->rules[i].compared_value,
-                       scomp_keys[ii->rules[i].size_compare_op],
-                       ii->rules[i].compared_size,
-                       action_keys[ii->rules[i].action],
-                       ii->rules[i].file_into,
-                       ii->rules[i].redirect_to,
-                       ii->rules[i].autoreply_message,
-                       final_keys[ii->rules[i].final_action]
-               );
-               base64_encoded_rule = malloc(StrLength(rule) * 2);
-               CtdlEncodeBase64(base64_encoded_rule, ChrPtr(rule), StrLength(rule), 0);
-               StrBufAppendPrintf(text, "rule|%d|%s|\n", i, base64_encoded_rule);
-               free(base64_encoded_rule);
-       }
-
-       char config_roomname[ROOMNAMELEN];
-       snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", usernum, USERCONFIGROOM);
-
-       // Save the config
-       rewrite_rules_to_disk(ChrPtr(text));
-
-       FreeStrBuf(&text);
-       FreeStrBuf(&rule);
-}
-
-
 /*
  * A user account is identified as requring inbox processing.
  * Do it.
@@ -860,11 +772,14 @@ void do_inbox_processing_for_user(long usernum) {
        struct CtdlMessage *msg;
        struct inboxrules *ii;
        char roomname[ROOMNAMELEN];
+       char username[64];
 
        if (CtdlGetUserByNumber(&CC->user, usernum) != 0) {     // grab the user record
                return;                                         // and bail out if we were given an invalid user
        }
 
+       strcpy(username, CC->user.fullname);                    // save the user name so we can fetch it later and lock it
+
        if (CC->user.msgnum_inboxrules <= 0) {
                return;                                         // this user has no inbox rules
        }
@@ -881,6 +796,13 @@ void do_inbox_processing_for_user(long usernum) {
                return;                                         // config message exists but body is null
        }
 
+       if (ii->lastproc > CC->user.lastproc_inboxrules) {      // There might be a "last message processed" number left over
+               CC->user.lastproc_inboxrules = ii->lastproc;    // in the ruleset from a previous version.  Use this if it is
+       }                                                       // a higher number.
+       else {
+               ii->lastproc = CC->user.lastproc_inboxrules;
+       }
+
        long original_lastproc = ii->lastproc;
        syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
 
@@ -890,9 +812,11 @@ void do_inbox_processing_for_user(long usernum) {
                CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
        }
 
-       // If we processed any new messages, reserialize and rewrite
+       // Record the number of the last message we processed
        if (ii->lastproc > original_lastproc) {
-               inbox_rewrite_rules(ii, usernum);
+               CtdlGetUserLock(&CC->user, username);
+               CC->user.lastproc_inboxrules = ii->lastproc;    // Avoid processing the entire inbox next time
+               CtdlPutUserLock(&CC->user);
        }
 
        // And free the memory.
@@ -1009,6 +933,27 @@ void cmd_gibr(char *argbuf) {
 }
 
 
+/*
+ * Rewrite the rule set to disk after it has been modified
+ * Called by cmd_pibr()
+ * Returns the msgnum of the new rules message
+ */
+void rewrite_rules_to_disk(const char *new_config) {
+       long old_msgnum = CC->user.msgnum_inboxrules;
+       char userconfigroomname[ROOMNAMELEN];
+       CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+       long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
+       CtdlGetUserLock(&CC->user, CC->curr_user);
+       CC->user.msgnum_inboxrules = new_msgnum;                // Now we know where to get the rules next time
+       CC->user.lastproc_inboxrules = new_msgnum;              // Avoid processing the entire inbox next time
+       CtdlPutUserLock(&CC->user);
+       if (old_msgnum > 0) {
+               syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+               CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+       }
+}
+
+
 /*
  * Put InBox Rules
  *
@@ -1032,24 +977,6 @@ void cmd_pibr(char *argbuf) {
                }
        }
        free(newrules);
-
-       // 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);
-       if (msg != NULL) {
-               if (!CM_IsEmpty(msg, eMesageText)) {
-                       rest = msg->cm_fields[eMesageText];
-                       while ((token = strtok_r(rest, "\n", &rest))) {
-                               // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
-                               if ((strncasecmp(token, "# WEBCIT_RULE|", 14)) && (strncasecmp(token, "rule|", 5)) && (haschar(token, '|'))) {
-                                       StrBufAppendBufPlain(NewConfig, token, -1, 0);
-                                       StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
-                               }
-                       }
-               }
-               CM_Free(msg);
-       }
-
        rewrite_rules_to_disk(ChrPtr(NewConfig));
        FreeStrBuf(&NewConfig);
 }