"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/inboxrules/serv_inboxrules.c" (5 Jun 2021, 29051 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "serv_inboxrules.c" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * Inbox handling rules
    3  *
    4  * Copyright (c) 1987-2020 by the citadel.org team
    5  *
    6  * This program is open source software; you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License version 3.
    8  *
    9  * This program is distributed in the hope that it will be useful,
   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12  * GNU General Public License for more details.
   13  */
   14 
   15 #include "sysdep.h"
   16 #include <stdlib.h>
   17 #include <unistd.h>
   18 #include <stdio.h>
   19 #include <fcntl.h>
   20 #include <ctype.h>
   21 #include <pwd.h>
   22 #include <errno.h>
   23 #include <sys/types.h>
   24 #include <time.h>
   25 #include <sys/wait.h>
   26 #include <string.h>
   27 #include <limits.h>
   28 #include <libcitadel.h>
   29 #include "citadel.h"
   30 #include "server.h"
   31 #include "citserver.h"
   32 #include "support.h"
   33 #include "config.h"
   34 #include "database.h"
   35 #include "msgbase.h"
   36 #include "internet_addressing.h"
   37 #include "ctdl_module.h"
   38 
   39 
   40 /*
   41  * The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
   42  */
   43 
   44 // Fields to be compared
   45 enum {
   46     field_from,     
   47     field_tocc,     
   48     field_subject,  
   49     field_replyto,  
   50     field_sender,   
   51     field_resentfrom,   
   52     field_resentto, 
   53     field_envfrom,  
   54     field_envto,    
   55     field_xmailer,  
   56     field_xspamflag,    
   57     field_xspamstatus,  
   58     field_listid,   
   59     field_size,     
   60     field_all
   61 };
   62 char *field_keys[] = {
   63     "from",
   64     "tocc",
   65     "subject",
   66     "replyto",
   67     "sender",
   68     "resentfrom",
   69     "resentto",
   70     "envfrom",
   71     "envto",
   72     "xmailer",
   73     "xspamflag",
   74     "xspamstatus",
   75     "listid",
   76     "size",
   77     "all"
   78 };
   79 
   80 // Field comparison operators
   81 enum {
   82     fcomp_contains,
   83     fcomp_notcontains,
   84     fcomp_is,
   85     fcomp_isnot,
   86     fcomp_matches,
   87     fcomp_notmatches
   88 };
   89 char *fcomp_keys[] = {
   90     "contains",
   91     "notcontains",
   92     "is",
   93     "isnot",
   94     "matches",
   95     "notmatches"
   96 };
   97 
   98 // Actions
   99 enum {
  100     action_keep,
  101     action_discard,
  102     action_reject,
  103     action_fileinto,
  104     action_redirect,
  105     action_vacation
  106 };
  107 char *action_keys[] = {
  108     "keep",
  109     "discard",
  110     "reject",
  111     "fileinto",
  112     "redirect",
  113     "vacation"
  114 };
  115 
  116 // Size comparison operators
  117 enum {
  118     scomp_larger,
  119     scomp_smaller
  120 };
  121 char *scomp_keys[] = {
  122     "larger",
  123     "smaller"
  124 };
  125 
  126 // Final actions
  127 enum {
  128     final_continue,
  129     final_stop
  130 };
  131 char *final_keys[] = {
  132     "continue",
  133     "stop"
  134 };
  135 
  136 // This data structure represents ONE inbox rule within the configuration.
  137 struct irule {
  138     int compared_field;
  139     int field_compare_op;
  140     char compared_value[128];
  141     int size_compare_op;
  142     long compared_size;
  143     int action;
  144     char file_into[ROOMNAMELEN];
  145     char redirect_to[1024];
  146     char autoreply_message[SIZ];
  147     int final_action;
  148 };
  149 
  150 // This data structure represents the entire inbox rules configuration AND current state for a single user.
  151 struct inboxrules {
  152     long lastproc;
  153     int num_rules;
  154     struct irule *rules;
  155 };
  156 
  157 
  158 // Destructor for 'struct inboxrules'
  159 void free_inbox_rules(struct inboxrules *ibr) {
  160     free(ibr->rules);
  161     free(ibr);
  162 }
  163 
  164 
  165 // Constructor for 'struct inboxrules' that deserializes the configuration from text input.
  166 struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
  167     int i;
  168 
  169     if (!serialized_rules) {
  170         return NULL;
  171     }
  172 
  173     /* Make a copy of the supplied buffer because we're going to shit all over it with strtok_r() */
  174     char *sr = strdup(serialized_rules);
  175     if (!sr) {
  176         return NULL;
  177     }
  178 
  179     struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
  180     if (ibr == NULL) {
  181         return NULL;
  182     }
  183     memset(ibr, 0, sizeof(struct inboxrules));
  184 
  185     char *token; 
  186     char *rest = sr;
  187     while ((token = strtok_r(rest, "\n", &rest))) {
  188 
  189         // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
  190         // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for later re-editing.
  191         // Now, the rules hidden in the comments become the real rules.
  192         if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
  193             strcpy(token, "rule|"); 
  194             strcpy(&token[5], &token[14]);
  195         }
  196 
  197         // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
  198         if (!strncasecmp(token, "rule|", 5)) {
  199             remove_token(&token[5], 0, '|');
  200             char *decoded_rule = malloc(strlen(token));
  201             CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
  202             ibr->num_rules++;
  203             ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
  204             struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
  205             memset(new_rule, 0, sizeof(struct irule));
  206 
  207             // We have a rule , now parse it
  208             char rtoken[SIZ];
  209             int nt = num_tokens(decoded_rule, '|');
  210             int t = 0;
  211             for (t=0; t<nt; ++t) {
  212                 extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
  213                 striplt(rtoken);
  214                 switch(t) {
  215                     case 1:                             // field to compare
  216                         for (i=0; i<=field_all; ++i) {
  217                             if (!strcasecmp(rtoken, field_keys[i])) {
  218                                 new_rule->compared_field = i;
  219                             }
  220                         }
  221                         break;
  222                     case 2:                             // field comparison operation
  223                         for (i=0; i<=fcomp_notmatches; ++i) {
  224                             if (!strcasecmp(rtoken, fcomp_keys[i])) {
  225                                 new_rule->field_compare_op = i;
  226                             }
  227                         }
  228                         break;
  229                     case 3:                             // field comparison value
  230                         safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
  231                         break;
  232                     case 4:                             // size comparison operation
  233                         for (i=0; i<=scomp_smaller; ++i) {
  234                             if (!strcasecmp(rtoken, scomp_keys[i])) {
  235                                 new_rule->size_compare_op = i;
  236                             }
  237                         }
  238                         break;
  239                     case 5:                             // size comparison value
  240                         new_rule->compared_size = atol(rtoken);
  241                         break;
  242                     case 6:                             // action
  243                         for (i=0; i<=action_vacation; ++i) {
  244                             if (!strcasecmp(rtoken, action_keys[i])) {
  245                                 new_rule->action = i;
  246                             }
  247                         }
  248                         break;
  249                     case 7:                             // file into (target room)
  250                         safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
  251                         break;
  252                     case 8:                             // redirect to (target address)
  253                         safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
  254                         break;
  255                     case 9:                             // autoreply message
  256                         safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
  257                         break;
  258                     case 10:                            // final_action;
  259                         for (i=0; i<=final_stop; ++i) {
  260                             if (!strcasecmp(rtoken, final_keys[i])) {
  261                                 new_rule->final_action = i;
  262                             }
  263                         }
  264                         break;
  265                     default:
  266                         break;
  267                 }
  268             }
  269             free(decoded_rule);
  270         }
  271 
  272         // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
  273         // This is a legacy location for this value and will only be used if it's the only one present.
  274         else if (!strncasecmp(token, "lastproc|", 5)) {
  275             ibr->lastproc = atol(&token[9]);
  276         }
  277 
  278         // Lines which do not contain a recognizable token must be IGNORED.  These lines may be left over
  279         // from a previous version and will disappear when we rewrite the config.
  280 
  281     }
  282 
  283     free(sr);       // free our copy of the source buffer that has now been trashed with null bytes...
  284     return(ibr);        // and return our complex data type to the caller.
  285 }
  286 
  287 
  288 // Perform the "fileinto" action (save the message in another room)
  289 // Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
  290 //
  291 int inbox_do_fileinto(struct irule *rule, long msgnum) {
  292     char *dest_folder = rule->file_into;
  293     char original_room_name[ROOMNAMELEN];
  294     char foldername[ROOMNAMELEN];
  295     int c;
  296 
  297     // Situations where we want to just keep the message in the inbox:
  298     if (
  299         (IsEmptyStr(dest_folder))           // no destination room was specified
  300         || (!strcasecmp(dest_folder, "INBOX"))      // fileinto inbox is the same as keep
  301         || (!strcasecmp(dest_folder, MAILROOM))     // fileinto "Mail" is the same as keep
  302     ) {
  303         return(1);                  // don't delete the inbox copy if this failed
  304     }
  305 
  306     // Remember what room we came from
  307     safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
  308 
  309     // First try a mailbox name match (check personal mail folders first)
  310     strcpy(foldername, original_room_name);                 // This keeps the user namespace of the inbox
  311     snprintf(&foldername[10], sizeof(foldername)-10, ".%s", dest_folder);   // And this tacks on the target room name
  312     c = CtdlGetRoom(&CC->room, foldername);
  313 
  314     // Then a regular room name match (public and private rooms)
  315     if (c != 0) {
  316         safestrncpy(foldername, dest_folder, sizeof foldername);
  317         c = CtdlGetRoom(&CC->room, foldername);
  318     }
  319 
  320     if (c != 0) {
  321         syslog(LOG_WARNING, "inboxrules: target <%s> does not exist", dest_folder);
  322         return(1);                  // don't delete the inbox copy if this failed
  323     }
  324 
  325     // Yes, we actually have to go there
  326     CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
  327 
  328     c = CtdlSaveMsgPointersInRoom(NULL, &msgnum, 1, 0, NULL, 0);
  329 
  330     // Go back to the room we came from
  331     if (strcasecmp(original_room_name, CC->room.QRname)) {
  332         CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
  333     }
  334 
  335     return(0);                      // delete the inbox copy
  336 }
  337 
  338 
  339 // Perform the "redirect" action (divert the message to another email address)
  340 // Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
  341 //
  342 int inbox_do_redirect(struct irule *rule, long msgnum) {
  343     if (IsEmptyStr(rule->redirect_to)) {
  344         syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
  345         return(1);                  // don't delete the inbox copy if this failed
  346     }
  347 
  348     struct recptypes *valid = validate_recipients(rule->redirect_to, NULL, 0);
  349     if (valid == NULL) {
  350         syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
  351         return(1);                  // don't delete the inbox copy if this failed
  352     }
  353     if (valid->num_error > 0) {
  354         free_recipients(valid);
  355         syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
  356         return(1);                  // don't delete the inbox copy if this failed
  357     }
  358 
  359     struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 1);
  360     if (msg == NULL) {
  361         free_recipients(valid);
  362         syslog(LOG_WARNING, "inboxrules: cannot reload message %ld for forwarding", msgnum);
  363         return(1);                  // don't delete the inbox copy if this failed
  364     }
  365 
  366     CtdlSubmitMsg(msg, valid, NULL);            // send the message to the new recipient
  367     free_recipients(valid);
  368     CM_Free(msg);
  369     return(0);                      // delete the inbox copy
  370 }
  371 
  372 
  373 /*
  374  * Perform the "reject" action (delete the message, and tell the sender we deleted it)
  375  */
  376 void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
  377     syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
  378 
  379     // If we can't determine who sent the message, reject silently.
  380     char *sender;
  381     if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
  382         sender = msg->cm_fields[eMessagePath];
  383     }
  384     else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
  385         sender = msg->cm_fields[erFc822Addr];
  386     }
  387     else {
  388         return;
  389     }
  390 
  391     // Assemble the reject message.
  392     char *reject_text = malloc(strlen(rule->autoreply_message) + 1024);
  393     if (reject_text == NULL) {
  394         return;
  395     }
  396     sprintf(reject_text, 
  397         "Content-type: text/plain\n"
  398         "\n"
  399         "The message was refused by the recipient's mail filtering program.\n"
  400         "The reason given was as follows:\n"
  401         "\n"
  402         "%s\n"
  403         "\n"
  404     ,
  405         rule->autoreply_message
  406     );
  407 
  408     // Deliver the message
  409     quickie_message(
  410         " ",
  411         msg->cm_fields[eenVelopeTo],
  412         sender,
  413         MAILROOM,
  414         reject_text,
  415         FMT_RFC822,
  416         "Delivery status notification"
  417     );
  418     free(reject_text);
  419 }
  420 
  421 
  422 /*
  423  * Perform the "vacation" action (send an automatic response)
  424  */
  425 void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
  426     syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
  427 
  428     // If we can't determine who sent the message, no auto-reply can be sent.
  429     char *sender;
  430     if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
  431         sender = msg->cm_fields[eMessagePath];
  432     }
  433     else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
  434         sender = msg->cm_fields[erFc822Addr];
  435     }
  436     else {
  437         return;
  438     }
  439 
  440     // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
  441     // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
  442     StrBuf *u = NewStrBuf();
  443     StrBufPrintf(u, "vacation/%x/%x/%x",
  444         HashLittle(sender, strlen(sender)),
  445         HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
  446         HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
  447     );
  448     int already_seen = CheckIfAlreadySeen(u);
  449     FreeStrBuf(&u);
  450 
  451     if (!already_seen) {
  452         // Assemble the auto-reply message.
  453         StrBuf *reject_text = NewStrBuf();
  454         if (reject_text == NULL) {
  455             return;
  456         }
  457 
  458         StrBufPrintf(reject_text, 
  459             "Content-type: text/plain\n"
  460             "\n"
  461             "%s\n"
  462             "\n"
  463         ,
  464             rule->autoreply_message
  465         );
  466     
  467         // Deliver the auto-reply.
  468         quickie_message(
  469             "",
  470             msg->cm_fields[eenVelopeTo],
  471             sender,
  472             MAILROOM,
  473             ChrPtr(reject_text),
  474             FMT_RFC822,
  475             "Delivery status notification"
  476         );
  477         FreeStrBuf(&reject_text);
  478     }
  479 }
  480 
  481 
  482 /*
  483  * Process a single message.  We know the room, the user, the rules, the message number, etc.
  484  */
  485 void inbox_do_msg(long msgnum, void *userdata) {
  486     struct inboxrules *ii = (struct inboxrules *) userdata;
  487     struct CtdlMessage *msg = NULL;     // If we are loading a message to process, put it here.
  488     int headers_loaded = 0;         // Did we load the headers yet?  Do it only once.
  489     int body_loaded = 0;            // Did we load the message body yet?  Do it only once.
  490     int metadata_loaded = 0;        // Did we load the metadata yet?  Do it only once.
  491     struct MetaData smi;            // If we are loading the metadata to compare, put it here.
  492     int rule_activated = 0;         // On each rule, this is set if the compare succeeds and the rule activates.
  493     char compare_me[SIZ];           // On each rule, we will store the field to be compared here.
  494     int compare_compound = 0;       // Set to 1 when we are comparing both display name and email address
  495     int keep_message = 1;           // Nonzero to keep the message in the inbox after processing, 0 to delete it.
  496     int i;
  497 
  498     syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
  499 
  500     if (ii->num_rules <= 0) {
  501         syslog(LOG_DEBUG, "inboxrules: rule set is empty");
  502         return;
  503     }
  504 
  505     for (i=0; i<ii->num_rules; ++i) {
  506         syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
  507         rule_activated = 0;
  508 
  509         // Before doing a field compare, check to see if we have the correct parts of the message in memory.
  510 
  511         switch(ii->rules[i].compared_field) {
  512             // These fields require loading only the top-level headers
  513             case field_from:        // From:
  514             case field_tocc:        // To: or Cc:
  515             case field_subject:     // Subject:
  516             case field_replyto:     // Reply-to:
  517             case field_listid:      // List-ID:
  518             case field_envto:       // Envelope-to:
  519             case field_envfrom:     // Return-path:
  520                 if (!headers_loaded) {
  521                     syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
  522                     msg = CtdlFetchMessage(msgnum, 0);
  523                     if (!msg) {
  524                         return;
  525                     }
  526                     headers_loaded = 1;
  527                 }
  528                 break;
  529             // These fields are not stored as Citadel headers, and therefore require a full message load.
  530             case field_sender:
  531             case field_resentfrom:
  532             case field_resentto:
  533             case field_xmailer:
  534             case field_xspamflag:
  535             case field_xspamstatus:
  536                 if (!body_loaded) {
  537                     syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
  538                     if (msg != NULL) {
  539                         CM_Free(msg);
  540                     }
  541                     msg = CtdlFetchMessage(msgnum, 1);
  542                     if (!msg) {
  543                         return;
  544                     }
  545                     headers_loaded = 1;
  546                     body_loaded = 1;
  547                 }
  548                 break;
  549             case field_size:
  550                 if (!metadata_loaded) {
  551                     syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
  552                     GetMetaData(&smi, msgnum);
  553                     metadata_loaded = 1;
  554                 }
  555                 break;
  556             case field_all:
  557                 syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
  558                 break;
  559             default:
  560                 syslog(LOG_DEBUG, "inboxrules: unknown rule key");
  561         }
  562 
  563         // If the rule involves a field comparison, load the field to be compared.
  564         compare_me[0] = 0;
  565         compare_compound = 0;
  566         switch(ii->rules[i].compared_field) {
  567             case field_from:        // From:
  568                 if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
  569                     snprintf(compare_me, sizeof compare_me, "%s|%s",
  570                         msg->cm_fields[eAuthor],
  571                         msg->cm_fields[erFc822Addr]
  572                     );
  573                     compare_compound = 1;       // there will be two fields to compare "name|address"
  574                 }
  575                 else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
  576                     safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
  577                 }
  578                 else if (!IsEmptyStr(msg->cm_fields[eAuthor])) {
  579                     safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
  580                 }
  581                 break;
  582             case field_tocc:        // To: or Cc:
  583                 if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
  584                     safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
  585                 }
  586                 if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
  587                     if (!IsEmptyStr(compare_me)) {
  588                         strcat(compare_me, ",");
  589                     }
  590                     safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
  591                 }
  592                 break;
  593             case field_subject:     // Subject:
  594                 if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
  595                     safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
  596                 }
  597                 break;
  598             case field_replyto:     // Reply-to:
  599                 if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
  600                     safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
  601                 }
  602                 break;
  603             case field_listid:      // List-ID:
  604                 if (!IsEmptyStr(msg->cm_fields[eListID])) {
  605                     safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
  606                 }
  607                 break;
  608             case field_envto:       // Envelope-to:
  609                 if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
  610                     safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
  611                 }
  612                 break;
  613             case field_envfrom:     // Return-path:
  614                 if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
  615                     safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
  616                 }
  617                 break;
  618 
  619             case field_sender:
  620             case field_resentfrom:
  621             case field_resentto:
  622             case field_xmailer:
  623             case field_xspamflag:
  624             case field_xspamstatus:
  625 
  626             default:
  627                 break;
  628         }
  629 
  630         // Message data to compare is loaded, now do something.
  631         switch(ii->rules[i].compared_field) {
  632             case field_from:        // From:
  633             case field_tocc:        // To: or Cc:
  634             case field_subject:     // Subject:
  635             case field_replyto:     // Reply-to:
  636             case field_listid:      // List-ID:
  637             case field_envto:       // Envelope-to:
  638             case field_envfrom:     // Return-path:
  639             case field_sender:
  640             case field_resentfrom:
  641             case field_resentto:
  642             case field_xmailer:
  643             case field_xspamflag:
  644             case field_xspamstatus:
  645 
  646                 // For all of the above fields, we can compare the field we've loaded into the buffer.
  647                 syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
  648                 int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
  649                 int exact_match = 0;
  650                 if (compare_compound) {
  651                     char *sep = strchr(compare_me, '|');
  652                     if (sep) {
  653                         *sep = 0;
  654                         exact_match =
  655                             (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
  656                             + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
  657                         ;
  658                     }
  659                 }
  660                 else {
  661                     exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
  662                 }
  663                 syslog(LOG_DEBUG, "substring match: %d", substring_match);
  664                 syslog(LOG_DEBUG, "exact match: %d", exact_match);
  665                 switch(ii->rules[i].field_compare_op) {
  666                     case fcomp_contains:
  667                     case fcomp_matches:
  668                         rule_activated = substring_match;
  669                         break;
  670                     case fcomp_notcontains:
  671                     case fcomp_notmatches:
  672                         rule_activated = !substring_match;
  673                         break;
  674                     case fcomp_is:
  675                         rule_activated = exact_match;
  676                         break;
  677                     case fcomp_isnot:
  678                         rule_activated = !exact_match;
  679                         break;
  680                 }
  681                 break;
  682 
  683             case field_size:
  684                 rule_activated = 0;
  685                 switch(ii->rules[i].field_compare_op) {
  686                     case scomp_larger:
  687                         rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
  688                         break;
  689                     case scomp_smaller:
  690                         rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
  691                         break;
  692                 }
  693                 break;
  694             case field_all:         // The "all messages" rule ALWAYS triggers
  695                 rule_activated = 1;
  696                 break;
  697             default:            // no matches, fall through and do nothing
  698                 syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
  699                 rule_activated = 0;
  700                 break;
  701         }
  702 
  703         // If the rule matched, perform the requested action.
  704         if (rule_activated) {
  705             syslog(LOG_DEBUG, "inboxrules: rule activated");
  706 
  707             // Perform the requested action
  708             switch(ii->rules[i].action) {
  709                 case action_keep:
  710                     keep_message = 1;
  711                     break;
  712                 case action_discard:
  713                     keep_message = 0;
  714                     break;
  715                 case action_reject:
  716                     inbox_do_reject(&ii->rules[i], msg);
  717                     keep_message = 0;
  718                     break;
  719                 case action_fileinto:
  720                     keep_message = inbox_do_fileinto(&ii->rules[i], msgnum);
  721                     break;
  722                 case action_redirect:
  723                     keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
  724                     break;
  725                 case action_vacation:
  726                     inbox_do_vacation(&ii->rules[i], msg);
  727                     keep_message = 1;
  728                     break;
  729             }
  730 
  731             // Now perform the "final" action (anything other than "stop" means continue)
  732             if (ii->rules[i].final_action == final_stop) {
  733                 syslog(LOG_DEBUG, "inboxrules: stop processing");
  734                 i = ii->num_rules + 1;                  // throw us out of scope to stop
  735             }
  736 
  737 
  738         }
  739         else {
  740             syslog(LOG_DEBUG, "inboxrules: rule not activated");
  741         }
  742     }
  743 
  744     if (msg != NULL) {      // Delete the copy of the message that is currently in memory.  We don't need it anymore.
  745         CM_Free(msg);
  746     }
  747 
  748     if (!keep_message) {        // Delete the copy of the message that is currently in the inbox, if rules dictated that.
  749         syslog(LOG_DEBUG, "inboxrules: delete %ld from inbox", msgnum);
  750         CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");            // we're in the inbox already
  751     }
  752 
  753     ii->lastproc = msgnum;      // make note of the last message we processed, so we don't scan the whole inbox again
  754 }
  755 
  756 
  757 /*
  758  * A user account is identified as requring inbox processing.
  759  * Do it.
  760  */
  761 void do_inbox_processing_for_user(long usernum) {
  762     struct CtdlMessage *msg;
  763     struct inboxrules *ii;
  764     char roomname[ROOMNAMELEN];
  765     char username[64];
  766 
  767     if (CtdlGetUserByNumber(&CC->user, usernum) != 0) { // grab the user record
  768         return;                     // and bail out if we were given an invalid user
  769     }
  770 
  771     strcpy(username, CC->user.fullname);            // save the user name so we can fetch it later and lock it
  772 
  773     if (CC->user.msgnum_inboxrules <= 0) {
  774         return;                     // this user has no inbox rules
  775     }
  776 
  777     msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
  778     if (msg == NULL) {
  779         return;                     // config msgnum is set but that message does not exist
  780     }
  781 
  782     ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
  783     CM_Free(msg);
  784 
  785     if (ii == NULL) {
  786         return;                     // config message exists but body is null
  787     }
  788 
  789     if (ii->lastproc > CC->user.lastproc_inboxrules) {  // There might be a "last message processed" number left over
  790         CC->user.lastproc_inboxrules = ii->lastproc;    // in the ruleset from a previous version.  Use this if it is
  791     }                           // a higher number.
  792     else {
  793         ii->lastproc = CC->user.lastproc_inboxrules;
  794     }
  795 
  796     long original_lastproc = ii->lastproc;
  797     syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
  798 
  799     // Go to the user's inbox room and process all new messages
  800     snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
  801     if (CtdlGetRoom(&CC->room, roomname) == 0) {
  802         CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
  803     }
  804 
  805     // Record the number of the last message we processed
  806     if (ii->lastproc > original_lastproc) {
  807         CtdlGetUserLock(&CC->user, username);
  808         CC->user.lastproc_inboxrules = ii->lastproc;    // Avoid processing the entire inbox next time
  809         CtdlPutUserLock(&CC->user);
  810     }
  811 
  812     // And free the memory.
  813     free_inbox_rules(ii);
  814 }
  815 
  816 
  817 /*
  818  * Here is an array of users (by number) who have received messages in their inbox and may require processing.
  819  */
  820 long *users_requiring_inbox_processing = NULL;
  821 int num_urip = 0;
  822 int num_urip_alloc = 0;
  823 
  824 
  825 /*
  826  * Perform inbox processing for all rooms which require it
  827  */
  828 void perform_inbox_processing(void) {
  829     int i = 0;
  830 
  831     if (num_urip == 0) {
  832         return;                                         // no action required
  833     }
  834 
  835     for (i=0; i<num_urip; ++i) {
  836         do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
  837     }
  838 
  839     free(users_requiring_inbox_processing);
  840     users_requiring_inbox_processing = NULL;
  841     num_urip = 0;
  842     num_urip_alloc = 0;
  843 }
  844 
  845 
  846 /*
  847  * This function is called after a message is saved to a room.
  848  * If it's someone's inbox, we have to check for inbox rules
  849  */
  850 int serv_inboxrules_roomhook(struct ctdlroom *room) {
  851     int i = 0;
  852 
  853     // Is this someone's inbox?
  854     if (!strcasecmp(&room->QRname[11], MAILROOM)) {
  855         long usernum = atol(room->QRname);
  856         if (usernum > 0) {
  857 
  858             // first check to see if this user is already on the list
  859             if (num_urip > 0) {
  860                 for (i=0; i<=num_urip; ++i) {
  861                     if (users_requiring_inbox_processing[i] == usernum) {       // already on the list!
  862                         return(0);
  863                     }
  864                 }
  865             }
  866 
  867             // make room if we need to
  868             if (num_urip_alloc == 0) {
  869                 num_urip_alloc = 100;
  870                 users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
  871             }
  872             else if (num_urip >= num_urip_alloc) {
  873                 num_urip_alloc += 100;
  874                 users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
  875             }
  876             
  877             // now add the user to the list
  878             users_requiring_inbox_processing[num_urip++] = usernum;
  879         }
  880     }
  881 
  882     // No errors are possible from this function.
  883     return(0);
  884 }
  885 
  886 
  887 
  888 /*
  889  * Get InBox Rules
  890  *
  891  * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
  892  * 
  893  * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
  894  */
  895 void cmd_gibr(char *argbuf) {
  896 
  897     if (CtdlAccessCheck(ac_logged_in)) return;
  898 
  899     cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
  900 
  901     struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
  902     if (msg != NULL) {
  903         if (!CM_IsEmpty(msg, eMesageText)) {
  904             char *token; 
  905             char *rest = msg->cm_fields[eMesageText];
  906             while ((token = strtok_r(rest, "\n", &rest))) {
  907 
  908                 // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
  909                 if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
  910                     strcpy(token, "rule|"); 
  911                     strcpy(&token[5], &token[14]);
  912                 }
  913 
  914                 // Output only lines containing rules.
  915                 if (!strncasecmp(token, "rule|", 5)) {
  916                         cprintf("%s\n", token); 
  917                 }
  918                 else {
  919                     cprintf("# invalid rule found : %s\n", token);
  920                 }
  921             }
  922         }
  923         CM_Free(msg);
  924     }
  925     cprintf("000\n");
  926 }
  927 
  928 
  929 /*
  930  * Rewrite the rule set to disk after it has been modified
  931  * Called by cmd_pibr()
  932  * Returns the msgnum of the new rules message
  933  */
  934 void rewrite_rules_to_disk(const char *new_config) {
  935     long old_msgnum = CC->user.msgnum_inboxrules;
  936     char userconfigroomname[ROOMNAMELEN];
  937     CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
  938     long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
  939     CtdlGetUserLock(&CC->user, CC->curr_user);
  940     CC->user.msgnum_inboxrules = new_msgnum;        // Now we know where to get the rules next time
  941     CC->user.lastproc_inboxrules = new_msgnum;      // Avoid processing the entire inbox next time
  942     CtdlPutUserLock(&CC->user);
  943     if (old_msgnum > 0) {
  944         syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
  945         CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
  946     }
  947 }
  948 
  949 
  950 /*
  951  * Put InBox Rules
  952  *
  953  * User transmits the new inbox rules for the account.  They are inserted into the account, replacing the ones already there.
  954  */
  955 void cmd_pibr(char *argbuf) {
  956     if (CtdlAccessCheck(ac_logged_in)) return;
  957 
  958     unbuffer_output();
  959     cprintf("%d send new rules\n", SEND_LISTING);
  960     char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
  961     StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
  962 
  963     char *token; 
  964     char *rest = newrules;
  965     while ((token = strtok_r(rest, "\n", &rest))) {
  966         // Accept only lines containing rules
  967         if (!strncasecmp(token, "rule|", 5)) {
  968             StrBufAppendBufPlain(NewConfig, token, -1, 0);
  969             StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
  970         }
  971     }
  972     free(newrules);
  973     rewrite_rules_to_disk(ChrPtr(NewConfig));
  974     FreeStrBuf(&NewConfig);
  975 }
  976 
  977 
  978 CTDL_MODULE_INIT(sieve)
  979 {
  980     if (!threading)
  981     {
  982         CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
  983         CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
  984         CtdlRegisterRoomHook(serv_inboxrules_roomhook);
  985         CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
  986     }
  987     
  988         /* return our module name for the log */
  989     return "inboxrules";
  990 }