"Fossies" - the Fresh Open Source Software Archive

Member "citadel/msgbase.c" (5 Jun 2021, 97132 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 "msgbase.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 // Implements the message store.
    2 //
    3 // Copyright (c) 1987-2021 by the citadel.org team
    4 //
    5 // This program is open source software; you can redistribute it and/or modify
    6 // it under the terms of the GNU General Public License version 3.
    7 //
    8 // This program is distributed in the hope that it will be useful,
    9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
   10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11 // GNU General Public License for more details.
   12 
   13 
   14 #include <stdlib.h>
   15 #include <unistd.h>
   16 #include <stdio.h>
   17 #include <regex.h>
   18 #include <sys/stat.h>
   19 #include <libcitadel.h>
   20 #include "ctdl_module.h"
   21 #include "citserver.h"
   22 #include "control.h"
   23 #include "config.h"
   24 #include "clientsocket.h"
   25 #include "genstamp.h"
   26 #include "room_ops.h"
   27 #include "user_ops.h"
   28 #include "internet_addressing.h"
   29 #include "euidindex.h"
   30 #include "msgbase.h"
   31 #include "journaling.h"
   32 
   33 struct addresses_to_be_filed *atbf = NULL;
   34 
   35 // These are the four-character field headers we use when outputting
   36 // messages in Citadel format (as opposed to RFC822 format).
   37 char *msgkeys[] = {
   38     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   39     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   40     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   41     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   42     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   43     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   44     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   45     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
   46     NULL, 
   47     "from", // A -> eAuthor
   48     NULL,   // B -> eBig_message
   49     NULL,   // C (formerly used as eRemoteRoom)
   50     NULL,   // D (formerly used as eDestination)
   51     "exti", // E -> eXclusivID
   52     "rfca", // F -> erFc822Addr
   53     NULL,   // G
   54     "hnod", // H (formerly used as eHumanNode)
   55     "msgn", // I -> emessageId
   56     "jrnl", // J -> eJournal
   57     "rep2", // K -> eReplyTo
   58     "list", // L -> eListID
   59     "text", // M -> eMesageText
   60     NULL,   // N (formerly used as eNodename)
   61     "room", // O -> eOriginalRoom
   62     "path", // P -> eMessagePath
   63     NULL,   // Q
   64     "rcpt", // R -> eRecipient
   65     NULL,   // S (formerly used as eSpecialField)
   66     "time", // T -> eTimestamp
   67     "subj", // U -> eMsgSubject
   68     "nvto", // V -> eenVelopeTo
   69     "wefw", // W -> eWeferences
   70     NULL,   // X
   71     "cccc", // Y -> eCarbonCopY
   72     NULL    // Z
   73 };
   74 
   75 
   76 HashList *msgKeyLookup = NULL;
   77 
   78 int GetFieldFromMnemonic(eMsgField *f, const char* c) {
   79     void *v = NULL;
   80     if (GetHash(msgKeyLookup, c, 4, &v)) {
   81         *f = (eMsgField) v;
   82         return 1;
   83     }
   84     return 0;
   85 }
   86 
   87 void FillMsgKeyLookupTable(void) {
   88     long i;
   89 
   90     msgKeyLookup = NewHash (1, FourHash);
   91 
   92     for (i=0; i < 91; i++) {
   93         if (msgkeys[i] != NULL) {
   94             Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
   95         }
   96     }
   97 }
   98 
   99 
  100 eMsgField FieldOrder[]  = {
  101 /* Important fields */
  102     emessageId   ,
  103     eMessagePath ,
  104     eTimestamp   ,
  105     eAuthor      ,
  106     erFc822Addr  ,
  107     eOriginalRoom,
  108     eRecipient   ,
  109 /* Semi-important fields */
  110     eBig_message ,
  111     eExclusiveID ,
  112     eWeferences  ,
  113     eJournal     ,
  114 /* G is not used yet */
  115     eReplyTo     ,
  116     eListID      ,
  117 /* Q is not used yet */
  118     eenVelopeTo  ,
  119 /* X is not used yet */
  120 /* Z is not used yet */
  121     eCarbonCopY  ,
  122     eMsgSubject  ,
  123 /* internal only */
  124     eErrorMsg    ,
  125     eSuppressIdx ,
  126     eExtnotify   ,
  127 /* Message text (MUST be last) */
  128     eMesageText 
  129 /* Not saved to disk: 
  130     eVltMsgNum
  131 */
  132 };
  133 
  134 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
  135 
  136 
  137 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
  138     return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
  139 }
  140 
  141 
  142 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
  143     if (Msg->cm_fields[which] != NULL) {
  144         free (Msg->cm_fields[which]);
  145     }
  146     if (length < 0) {           // You can set the length to -1 to have CM_SetField measure it for you
  147         length = strlen(buf);
  148     }
  149     Msg->cm_fields[which] = malloc(length + 1);
  150     memcpy(Msg->cm_fields[which], buf, length);
  151     Msg->cm_fields[which][length] = '\0';
  152     Msg->cm_lengths[which] = length;
  153 }
  154 
  155 
  156 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
  157     char buf[128];
  158     long len;
  159     len = snprintf(buf, sizeof(buf), "%ld", lvalue);
  160     CM_SetField(Msg, which, buf, len);
  161 }
  162 
  163 
  164 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
  165     if (Msg->cm_fields[WhichToCut] == NULL)
  166         return;
  167 
  168     if (Msg->cm_lengths[WhichToCut] > maxlen)
  169     {
  170         Msg->cm_fields[WhichToCut][maxlen] = '\0';
  171         Msg->cm_lengths[WhichToCut] = maxlen;
  172     }
  173 }
  174 
  175 
  176 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
  177     if (Msg->cm_fields[which] != NULL)
  178         free (Msg->cm_fields[which]);
  179     Msg->cm_fields[which] = NULL;
  180     Msg->cm_lengths[which] = 0;
  181 }
  182 
  183 
  184 void CM_Flush(struct CtdlMessage *Msg) {
  185     int i;
  186 
  187     if (CM_IsValidMsg(Msg) == 0) {
  188         return;
  189     }
  190 
  191     for (i = 0; i < 256; ++i) {
  192         CM_FlushField(Msg, i);
  193     }
  194 }
  195 
  196 
  197 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
  198     long len;
  199     if (Msg->cm_fields[WhichToPutTo] != NULL) {
  200         free (Msg->cm_fields[WhichToPutTo]);
  201     }
  202 
  203     if (Msg->cm_fields[WhichtToCopy] != NULL) {
  204         len = Msg->cm_lengths[WhichtToCopy];
  205         Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
  206         memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
  207         Msg->cm_fields[WhichToPutTo][len] = '\0';
  208         Msg->cm_lengths[WhichToPutTo] = len;
  209     }
  210     else {
  211         Msg->cm_fields[WhichToPutTo] = NULL;
  212         Msg->cm_lengths[WhichToPutTo] = 0;
  213     }
  214 }
  215 
  216 
  217 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
  218     if (Msg->cm_fields[which] != NULL) {
  219         long oldmsgsize;
  220         long newmsgsize;
  221         char *new;
  222 
  223         oldmsgsize = Msg->cm_lengths[which] + 1;
  224         newmsgsize = length + oldmsgsize;
  225 
  226         new = malloc(newmsgsize);
  227         memcpy(new, buf, length);
  228         memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
  229         free(Msg->cm_fields[which]);
  230         Msg->cm_fields[which] = new;
  231         Msg->cm_lengths[which] = newmsgsize - 1;
  232     }
  233     else {
  234         Msg->cm_fields[which] = malloc(length + 1);
  235         memcpy(Msg->cm_fields[which], buf, length);
  236         Msg->cm_fields[which][length] = '\0';
  237         Msg->cm_lengths[which] = length;
  238     }
  239 }
  240 
  241 
  242 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
  243     if (Msg->cm_fields[which] != NULL) {
  244         free (Msg->cm_fields[which]);
  245     }
  246 
  247     Msg->cm_fields[which] = *buf;
  248     *buf = NULL;
  249     if (length < 0) {           // You can set the length to -1 to have CM_SetField measure it for you
  250         Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
  251     }
  252     else {
  253         Msg->cm_lengths[which] = length;
  254     }
  255 }
  256 
  257 
  258 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
  259     if (Msg->cm_fields[which] != NULL) {
  260         free (Msg->cm_fields[which]);
  261     }
  262 
  263     Msg->cm_lengths[which] = StrLength(*buf);
  264     Msg->cm_fields[which] = SmashStrBuf(buf);
  265 }
  266 
  267 
  268 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
  269     if (Msg->cm_fields[which] != NULL) {
  270         *retlen = Msg->cm_lengths[which];
  271         *ret = Msg->cm_fields[which];
  272         Msg->cm_fields[which] = NULL;
  273         Msg->cm_lengths[which] = 0;
  274     }
  275     else {
  276         *ret = NULL;
  277         *retlen = 0;
  278     }
  279 }
  280 
  281 
  282 // Returns 1 if the supplied pointer points to a valid Citadel message.
  283 // If the pointer is NULL or the magic number check fails, returns 0.
  284 int CM_IsValidMsg(struct CtdlMessage *msg) {
  285     if (msg == NULL) {
  286         return 0;
  287     }
  288     if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
  289         syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
  290         return 0;
  291     }
  292     return 1;
  293 }
  294 
  295 
  296 void CM_FreeContents(struct CtdlMessage *msg) {
  297     int i;
  298 
  299     for (i = 0; i < 256; ++i)
  300         if (msg->cm_fields[i] != NULL) {
  301             free(msg->cm_fields[i]);
  302             msg->cm_lengths[i] = 0;
  303         }
  304 
  305     msg->cm_magic = 0;  // just in case
  306 }
  307 
  308 
  309 // 'Destructor' for struct CtdlMessage
  310 void CM_Free(struct CtdlMessage *msg) {
  311     if (CM_IsValidMsg(msg) == 0) {
  312         if (msg != NULL) free (msg);
  313         return;
  314     }
  315     CM_FreeContents(msg);
  316     free(msg);
  317 }
  318 
  319 
  320 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
  321     long len;
  322     len = OrgMsg->cm_lengths[i];
  323     NewMsg->cm_fields[i] = malloc(len + 1);
  324     if (NewMsg->cm_fields[i] == NULL) {
  325         return 0;
  326     }
  327     memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
  328     NewMsg->cm_fields[i][len] = '\0';
  329     NewMsg->cm_lengths[i] = len;
  330     return 1;
  331 }
  332 
  333 
  334 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
  335     int i;
  336     struct CtdlMessage *NewMsg;
  337 
  338     if (CM_IsValidMsg(OrgMsg) == 0) {
  339         return NULL;
  340     }
  341     NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
  342     if (NewMsg == NULL) {
  343         return NULL;
  344     }
  345 
  346     memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
  347 
  348     memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
  349     
  350     for (i = 0; i < 256; ++i) {
  351         if (OrgMsg->cm_fields[i] != NULL) {
  352             if (!CM_DupField(i, OrgMsg, NewMsg)) {
  353                 CM_Free(NewMsg);
  354                 return NULL;
  355             }
  356         }
  357     }
  358 
  359     return NewMsg;
  360 }
  361 
  362 
  363 // Determine if a given message matches the fields in a message template.
  364 // Return 0 for a successful match.
  365 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
  366     int i;
  367 
  368     // If there aren't any fields in the template, all messages will match.
  369     if (template == NULL) return(0);
  370 
  371     // Null messages are bogus.
  372     if (msg == NULL) return(1);
  373 
  374     for (i='A'; i<='Z'; ++i) {
  375         if (template->cm_fields[i] != NULL) {
  376             if (msg->cm_fields[i] == NULL) {
  377                 // Considered equal if temmplate is empty string
  378                 if (IsEmptyStr(template->cm_fields[i])) continue;
  379                 return 1;
  380             }
  381             if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
  382                 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
  383                 return 1;
  384         }
  385     }
  386 
  387     /* All compares succeeded: we have a match! */
  388     return 0;
  389 }
  390 
  391 
  392 // Retrieve the "seen" message list for the current room.
  393 void CtdlGetSeen(char *buf, int which_set) {
  394     visit vbuf;
  395 
  396     // Learn about the user and room in question
  397     CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
  398 
  399     if (which_set == ctdlsetseen_seen) {
  400         safestrncpy(buf, vbuf.v_seen, SIZ);
  401     }
  402     if (which_set == ctdlsetseen_answered) {
  403         safestrncpy(buf, vbuf.v_answered, SIZ);
  404     }
  405 }
  406 
  407 
  408 // Manipulate the "seen msgs" string (or other message set strings)
  409 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
  410         int target_setting, int which_set,
  411         struct ctdluser *which_user, struct ctdlroom *which_room) {
  412     struct cdbdata *cdbfr;
  413     int i, k;
  414     int is_seen = 0;
  415     int was_seen = 0;
  416     long lo = (-1L);
  417     long hi = (-1L);
  418     visit vbuf;
  419     long *msglist;
  420     int num_msgs = 0;
  421     StrBuf *vset;
  422     StrBuf *setstr;
  423     StrBuf *lostr;
  424     StrBuf *histr;
  425     const char *pvset;
  426     char *is_set;   // actually an array of booleans
  427 
  428     // Don't bother doing *anything* if we were passed a list of zero messages
  429     if (num_target_msgnums < 1) {
  430         return;
  431     }
  432 
  433     // If no room was specified, we go with the current room.
  434     if (!which_room) {
  435         which_room = &CC->room;
  436     }
  437 
  438     // If no user was specified, we go with the current user.
  439     if (!which_user) {
  440         which_user = &CC->user;
  441     }
  442 
  443     syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
  444            num_target_msgnums, target_msgnums[0],
  445            (target_setting ? "SET" : "CLEAR"),
  446            which_set,
  447            which_room->QRname);
  448 
  449     // Learn about the user and room in question
  450     CtdlGetRelationship(&vbuf, which_user, which_room);
  451 
  452     // Load the message list
  453     cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
  454     if (cdbfr != NULL) {
  455         msglist = (long *) cdbfr->ptr;
  456         cdbfr->ptr = NULL;  // CtdlSetSeen() now owns this memory
  457         num_msgs = cdbfr->len / sizeof(long);
  458         cdb_free(cdbfr);
  459     }
  460     else {
  461         return; // No messages at all?  No further action.
  462     }
  463 
  464     is_set = malloc(num_msgs * sizeof(char));
  465     memset(is_set, 0, (num_msgs * sizeof(char)) );
  466 
  467     /* Decide which message set we're manipulating */
  468     switch(which_set) {
  469     case ctdlsetseen_seen:
  470         vset = NewStrBufPlain(vbuf.v_seen, -1);
  471         break;
  472     case ctdlsetseen_answered:
  473         vset = NewStrBufPlain(vbuf.v_answered, -1);
  474         break;
  475     default:
  476         vset = NewStrBuf();
  477     }
  478 
  479 
  480 #if 0   // This is a special diagnostic section.  Do not allow it to run during normal operation.
  481     syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
  482     for (i=0; i<num_msgs; ++i) {
  483         if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
  484     }
  485     syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
  486     for (k=0; k<num_target_msgnums; ++k) {
  487         if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
  488     }
  489 #endif
  490 
  491     // Translate the existing sequence set into an array of booleans
  492     setstr = NewStrBuf();
  493     lostr = NewStrBuf();
  494     histr = NewStrBuf();
  495     pvset = NULL;
  496     while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
  497 
  498         StrBufExtract_token(lostr, setstr, 0, ':');
  499         if (StrBufNum_tokens(setstr, ':') >= 2) {
  500             StrBufExtract_token(histr, setstr, 1, ':');
  501         }
  502         else {
  503             FlushStrBuf(histr);
  504             StrBufAppendBuf(histr, lostr, 0);
  505         }
  506         lo = StrTol(lostr);
  507         if (!strcmp(ChrPtr(histr), "*")) {
  508             hi = LONG_MAX;
  509         }
  510         else {
  511             hi = StrTol(histr);
  512         }
  513 
  514         for (i = 0; i < num_msgs; ++i) {
  515             if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
  516                 is_set[i] = 1;
  517             }
  518         }
  519     }
  520     FreeStrBuf(&setstr);
  521     FreeStrBuf(&lostr);
  522     FreeStrBuf(&histr);
  523 
  524     // Now translate the array of booleans back into a sequence set
  525     FlushStrBuf(vset);
  526     was_seen = 0;
  527     lo = (-1);
  528     hi = (-1);
  529 
  530     for (i=0; i<num_msgs; ++i) {
  531         is_seen = is_set[i];
  532 
  533         // Apply changes
  534         for (k=0; k<num_target_msgnums; ++k) {
  535             if (msglist[i] == target_msgnums[k]) {
  536                 is_seen = target_setting;
  537             }
  538         }
  539 
  540         if ((was_seen == 0) && (is_seen == 1)) {
  541             lo = msglist[i];
  542         }
  543         else if ((was_seen == 1) && (is_seen == 0)) {
  544             hi = msglist[i-1];
  545 
  546             if (StrLength(vset) > 0) {
  547                 StrBufAppendBufPlain(vset, HKEY(","), 0);
  548             }
  549             if (lo == hi) {
  550                 StrBufAppendPrintf(vset, "%ld", hi);
  551             }
  552             else {
  553                 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
  554             }
  555         }
  556 
  557         if ((is_seen) && (i == num_msgs - 1)) {
  558             if (StrLength(vset) > 0) {
  559                 StrBufAppendBufPlain(vset, HKEY(","), 0);
  560             }
  561             if ((i==0) || (was_seen == 0)) {
  562                 StrBufAppendPrintf(vset, "%ld", msglist[i]);
  563             }
  564             else {
  565                 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
  566             }
  567         }
  568 
  569         was_seen = is_seen;
  570     }
  571 
  572     // We will have to stuff this string back into a 4096 byte buffer, so if it's
  573     // larger than that now, truncate it by removing tokens from the beginning.
  574     // The limit of 100 iterations is there to prevent an infinite loop in case
  575     // something unexpected happens.
  576     int number_of_truncations = 0;
  577     while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
  578         StrBufRemove_token(vset, 0, ',');
  579         ++number_of_truncations;
  580     }
  581 
  582     // If we're truncating the sequence set of messages marked with the 'seen' flag,
  583     // we want the earliest messages (the truncated ones) to be marked, not unmarked.
  584     // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
  585     if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
  586         StrBuf *first_tok;
  587         first_tok = NewStrBuf();
  588         StrBufExtract_token(first_tok, vset, 0, ',');
  589         StrBufRemove_token(vset, 0, ',');
  590 
  591         if (StrBufNum_tokens(first_tok, ':') > 1) {
  592             StrBufRemove_token(first_tok, 0, ':');
  593         }
  594         
  595         StrBuf *new_set;
  596         new_set = NewStrBuf();
  597         StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
  598         StrBufAppendBuf(new_set, first_tok, 0);
  599         StrBufAppendBufPlain(new_set, HKEY(":"), 0);
  600         StrBufAppendBuf(new_set, vset, 0);
  601 
  602         FreeStrBuf(&vset);
  603         FreeStrBuf(&first_tok);
  604         vset = new_set;
  605     }
  606 
  607     // Decide which message set we're manipulating
  608     switch (which_set) {
  609         case ctdlsetseen_seen:
  610             safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
  611             break;
  612         case ctdlsetseen_answered:
  613             safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
  614             break;
  615     }
  616 
  617     free(is_set);
  618     free(msglist);
  619     CtdlSetRelationship(&vbuf, which_user, which_room);
  620     FreeStrBuf(&vset);
  621 }
  622 
  623 
  624 // API function to perform an operation for each qualifying message in the
  625 // current room.  (Returns the number of messages processed.)
  626 int CtdlForEachMessage(int mode, long ref, char *search_string,
  627             char *content_type,
  628             struct CtdlMessage *compare,
  629                         ForEachMsgCallback CallBack,
  630             void *userdata)
  631 {
  632     int a, i, j;
  633     visit vbuf;
  634     struct cdbdata *cdbfr;
  635     long *msglist = NULL;
  636     int num_msgs = 0;
  637     int num_processed = 0;
  638     long thismsg;
  639     struct MetaData smi;
  640     struct CtdlMessage *msg = NULL;
  641     int is_seen = 0;
  642     long lastold = 0L;
  643     int printed_lastold = 0;
  644     int num_search_msgs = 0;
  645     long *search_msgs = NULL;
  646     regex_t re;
  647     int need_to_free_re = 0;
  648     regmatch_t pm;
  649 
  650     if ((content_type) && (!IsEmptyStr(content_type))) {
  651         regcomp(&re, content_type, 0);
  652         need_to_free_re = 1;
  653     }
  654 
  655     /* Learn about the user and room in question */
  656     if (server_shutting_down) {
  657         if (need_to_free_re) regfree(&re);
  658         return -1;
  659     }
  660     CtdlGetUser(&CC->user, CC->curr_user);
  661 
  662     if (server_shutting_down) {
  663         if (need_to_free_re) regfree(&re);
  664         return -1;
  665     }
  666     CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
  667 
  668     if (server_shutting_down) {
  669         if (need_to_free_re) regfree(&re);
  670         return -1;
  671     }
  672 
  673     /* Load the message list */
  674     cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
  675     if (cdbfr == NULL) {
  676         if (need_to_free_re) regfree(&re);
  677         return 0;   /* No messages at all?  No further action. */
  678     }
  679 
  680     msglist = (long *) cdbfr->ptr;
  681     num_msgs = cdbfr->len / sizeof(long);
  682 
  683     cdbfr->ptr = NULL;  /* clear this so that cdb_free() doesn't free it */
  684     cdb_free(cdbfr);    /* we own this memory now */
  685 
  686     /*
  687      * Now begin the traversal.
  688      */
  689     if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
  690 
  691         /* If the caller is looking for a specific MIME type, filter
  692          * out all messages which are not of the type requested.
  693          */
  694         if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
  695 
  696             /* This call to GetMetaData() sits inside this loop
  697              * so that we only do the extra database read per msg
  698              * if we need to.  Doing the extra read all the time
  699              * really kills the server.  If we ever need to use
  700              * metadata for another search criterion, we need to
  701              * move the read somewhere else -- but still be smart
  702              * enough to only do the read if the caller has
  703              * specified something that will need it.
  704              */
  705             if (server_shutting_down) {
  706                 if (need_to_free_re) regfree(&re);
  707                 free(msglist);
  708                 return -1;
  709             }
  710             GetMetaData(&smi, msglist[a]);
  711 
  712             /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
  713             if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
  714                 msglist[a] = 0L;
  715             }
  716         }
  717     }
  718 
  719     num_msgs = sort_msglist(msglist, num_msgs);
  720 
  721     /* If a template was supplied, filter out the messages which
  722      * don't match.  (This could induce some delays!)
  723      */
  724     if (num_msgs > 0) {
  725         if (compare != NULL) {
  726             for (a = 0; a < num_msgs; ++a) {
  727                 if (server_shutting_down) {
  728                     if (need_to_free_re) regfree(&re);
  729                     free(msglist);
  730                     return -1;
  731                 }
  732                 msg = CtdlFetchMessage(msglist[a], 1);
  733                 if (msg != NULL) {
  734                     if (CtdlMsgCmp(msg, compare)) {
  735                         msglist[a] = 0L;
  736                     }
  737                     CM_Free(msg);
  738                 }
  739             }
  740         }
  741     }
  742 
  743     /* If a search string was specified, get a message list from
  744      * the full text index and remove messages which aren't on both
  745      * lists.
  746      *
  747      * How this works:
  748      * Since the lists are sorted and strictly ascending, and the
  749      * output list is guaranteed to be shorter than or equal to the
  750      * input list, we overwrite the bottom of the input list.  This
  751      * eliminates the need to memmove big chunks of the list over and
  752      * over again.
  753      */
  754     if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
  755 
  756         /* Call search module via hook mechanism.
  757          * NULL means use any search function available.
  758          * otherwise replace with a char * to name of search routine
  759          */
  760         CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
  761 
  762         if (num_search_msgs > 0) {
  763     
  764             int orig_num_msgs;
  765 
  766             orig_num_msgs = num_msgs;
  767             num_msgs = 0;
  768             for (i=0; i<orig_num_msgs; ++i) {
  769                 for (j=0; j<num_search_msgs; ++j) {
  770                     if (msglist[i] == search_msgs[j]) {
  771                         msglist[num_msgs++] = msglist[i];
  772                     }
  773                 }
  774             }
  775         }
  776         else {
  777             num_msgs = 0;   /* No messages qualify */
  778         }
  779         if (search_msgs != NULL) free(search_msgs);
  780 
  781         /* Now that we've purged messages which don't contain the search
  782          * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
  783          * point on.
  784          */
  785         mode = MSGS_ALL;
  786     }
  787 
  788     /*
  789      * Now iterate through the message list, according to the
  790      * criteria supplied by the caller.
  791      */
  792     if (num_msgs > 0)
  793         for (a = 0; a < num_msgs; ++a) {
  794             if (server_shutting_down) {
  795                 if (need_to_free_re) regfree(&re);
  796                 free(msglist);
  797                 return num_processed;
  798             }
  799             thismsg = msglist[a];
  800             if (mode == MSGS_ALL) {
  801                 is_seen = 0;
  802             }
  803             else {
  804                 is_seen = is_msg_in_sequence_set(
  805                             vbuf.v_seen, thismsg);
  806                 if (is_seen) lastold = thismsg;
  807             }
  808             if ((thismsg > 0L)
  809                 && (
  810 
  811                        (mode == MSGS_ALL)
  812                        || ((mode == MSGS_OLD) && (is_seen))
  813                        || ((mode == MSGS_NEW) && (!is_seen))
  814                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
  815                    || ((mode == MSGS_FIRST) && (a < ref))
  816                 || ((mode == MSGS_GT) && (thismsg > ref))
  817                 || ((mode == MSGS_LT) && (thismsg < ref))
  818                 || ((mode == MSGS_EQ) && (thismsg == ref))
  819                 )
  820                 ) {
  821                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
  822                     if (CallBack) {
  823                         CallBack(lastold, userdata);
  824                     }
  825                     printed_lastold = 1;
  826                     ++num_processed;
  827                 }
  828                 if (CallBack) {
  829                     CallBack(thismsg, userdata);
  830                 }
  831                 ++num_processed;
  832             }
  833         }
  834     if (need_to_free_re) regfree(&re);
  835 
  836     /*
  837      * We cache the most recent msglist in order to do security checks later
  838      */
  839     if (CC->client_socket > 0) {
  840         if (CC->cached_msglist != NULL) {
  841             free(CC->cached_msglist);
  842         }
  843         CC->cached_msglist = msglist;
  844         CC->cached_num_msgs = num_msgs;
  845     }
  846     else {
  847         free(msglist);
  848     }
  849 
  850     return num_processed;
  851 }
  852 
  853 
  854 /*
  855  * memfmout()  -  Citadel text formatter and paginator.
  856  *       Although the original purpose of this routine was to format
  857  *       text to the reader's screen width, all we're really using it
  858  *       for here is to format text out to 80 columns before sending it
  859  *       to the client.  The client software may reformat it again.
  860  */
  861 void memfmout(
  862     char *mptr,     /* where are we going to get our text from? */
  863     const char *nl      /* string to terminate lines with */
  864 ) {
  865     int column = 0;
  866     unsigned char ch = 0;
  867     char outbuf[1024];
  868     int len = 0;
  869     int nllen = 0;
  870 
  871     if (!mptr) return;
  872     nllen = strlen(nl);
  873     while (ch=*(mptr++), ch != 0) {
  874 
  875         if (ch == '\n') {
  876             if (client_write(outbuf, len) == -1) {
  877                 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  878                 return;
  879             }
  880             len = 0;
  881             if (client_write(nl, nllen) == -1) {
  882                 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  883                 return;
  884             }
  885             column = 0;
  886         }
  887         else if (ch == '\r') {
  888             /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
  889         }
  890         else if (isspace(ch)) {
  891             if (column > 72) {      /* Beyond 72 columns, break on the next space */
  892                 if (client_write(outbuf, len) == -1) {
  893                     syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  894                     return;
  895                 }
  896                 len = 0;
  897                 if (client_write(nl, nllen) == -1) {
  898                     syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  899                     return;
  900                 }
  901                 column = 0;
  902             }
  903             else {
  904                 outbuf[len++] = ch;
  905                 ++column;
  906             }
  907         }
  908         else {
  909             outbuf[len++] = ch;
  910             ++column;
  911             if (column > 1000) {        /* Beyond 1000 columns, break anywhere */
  912                 if (client_write(outbuf, len) == -1) {
  913                     syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  914                     return;
  915                 }
  916                 len = 0;
  917                 if (client_write(nl, nllen) == -1) {
  918                     syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
  919                     return;
  920                 }
  921                 column = 0;
  922             }
  923         }
  924     }
  925     if (len) {
  926         if (client_write(outbuf, len) == -1) {
  927             syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
  928             return;
  929         }
  930         client_write(nl, nllen);
  931         column = 0;
  932     }
  933 }
  934 
  935 
  936 /*
  937  * Callback function for mime parser that simply lists the part
  938  */
  939 void list_this_part(char *name, char *filename, char *partnum, char *disp,
  940             void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
  941             char *cbid, void *cbuserdata)
  942 {
  943     struct ma_info *ma;
  944     
  945     ma = (struct ma_info *)cbuserdata;
  946     if (ma->is_ma == 0) {
  947         cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
  948             name, 
  949             filename, 
  950             partnum, 
  951             disp, 
  952             cbtype, 
  953             (long)length, 
  954             cbid, 
  955             cbcharset);
  956     }
  957 }
  958 
  959 
  960 /* 
  961  * Callback function for multipart prefix
  962  */
  963 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
  964             void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
  965             char *cbid, void *cbuserdata)
  966 {
  967     struct ma_info *ma;
  968     
  969     ma = (struct ma_info *)cbuserdata;
  970     if (!strcasecmp(cbtype, "multipart/alternative")) {
  971         ++ma->is_ma;
  972     }
  973 
  974     if (ma->is_ma == 0) {
  975         cprintf("pref=%s|%s\n", partnum, cbtype);
  976     }
  977 }
  978 
  979 
  980 /* 
  981  * Callback function for multipart sufffix
  982  */
  983 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
  984             void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
  985             char *cbid, void *cbuserdata)
  986 {
  987     struct ma_info *ma;
  988     
  989     ma = (struct ma_info *)cbuserdata;
  990     if (ma->is_ma == 0) {
  991         cprintf("suff=%s|%s\n", partnum, cbtype);
  992     }
  993     if (!strcasecmp(cbtype, "multipart/alternative")) {
  994         --ma->is_ma;
  995     }
  996 }
  997 
  998 
  999 /*
 1000  * Callback function for mime parser that opens a section for downloading
 1001  * we use serv_files function here: 
 1002  */
 1003 extern void OpenCmdResult(char *filename, const char *mime_type);
 1004 void mime_download(char *name, char *filename, char *partnum, char *disp,
 1005            void *content, char *cbtype, char *cbcharset, size_t length,
 1006            char *encoding, char *cbid, void *cbuserdata)
 1007 {
 1008     int rv = 0;
 1009 
 1010     /* Silently go away if there's already a download open. */
 1011     if (CC->download_fp != NULL)
 1012         return;
 1013 
 1014     if (
 1015         (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
 1016     ||  (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
 1017     ) {
 1018         CC->download_fp = tmpfile();
 1019         if (CC->download_fp == NULL) {
 1020             syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
 1021             cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
 1022             return;
 1023         }
 1024     
 1025         rv = fwrite(content, length, 1, CC->download_fp);
 1026         if (rv <= 0) {
 1027             syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
 1028             cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
 1029             fclose(CC->download_fp);
 1030             CC->download_fp = NULL;
 1031             return;
 1032         }
 1033         fflush(CC->download_fp);
 1034         rewind(CC->download_fp);
 1035     
 1036         OpenCmdResult(filename, cbtype);
 1037     }
 1038 }
 1039 
 1040 
 1041 /*
 1042  * Callback function for mime parser that outputs a section all at once.
 1043  * We can specify the desired section by part number *or* content-id.
 1044  */
 1045 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
 1046            void *content, char *cbtype, char *cbcharset, size_t length,
 1047            char *encoding, char *cbid, void *cbuserdata)
 1048 {
 1049     int *found_it = (int *)cbuserdata;
 1050 
 1051     if (
 1052         (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
 1053     ||  (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
 1054     ) {
 1055         *found_it = 1;
 1056         cprintf("%d %d|-1|%s|%s|%s\n",
 1057             BINARY_FOLLOWS,
 1058             (int)length,
 1059             filename,
 1060             cbtype,
 1061             cbcharset
 1062         );
 1063         client_write(content, length);
 1064     }
 1065 }
 1066 
 1067 
 1068 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
 1069 {
 1070     struct CtdlMessage *ret = NULL;
 1071     const char *mptr;
 1072     const char *upper_bound;
 1073     cit_uint8_t ch;
 1074     cit_uint8_t field_header;
 1075     eMsgField which;
 1076 
 1077     mptr = Buffer;
 1078     upper_bound = Buffer + Length;
 1079     if (msgnum <= 0) {
 1080         return NULL;
 1081     }
 1082 
 1083     // Parse the three bytes that begin EVERY message on disk.
 1084     // The first is always 0xFF, the on-disk magic number.
 1085     // The second is the anonymous/public type byte.
 1086     // The third is the format type byte (vari, fixed, or MIME).
 1087     //
 1088     ch = *mptr++;
 1089     if (ch != 255) {
 1090         syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
 1091         return NULL;
 1092     }
 1093     ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
 1094     memset(ret, 0, sizeof(struct CtdlMessage));
 1095 
 1096     ret->cm_magic = CTDLMESSAGE_MAGIC;
 1097     ret->cm_anon_type = *mptr++;                // Anon type byte
 1098     ret->cm_format_type = *mptr++;              // Format type byte
 1099 
 1100     // The rest is zero or more arbitrary fields.  Load them in.
 1101     // We're done when we encounter either a zero-length field or
 1102     // have just processed the 'M' (message text) field.
 1103     //
 1104     do {
 1105         field_header = '\0';
 1106         long len;
 1107 
 1108         while (field_header == '\0') {          // work around possibly buggy messages
 1109             if (mptr >= upper_bound) {
 1110                 break;
 1111             }
 1112             field_header = *mptr++;
 1113         }
 1114         if (mptr >= upper_bound) {
 1115             break;
 1116         }
 1117         which = field_header;
 1118         len = strlen(mptr);
 1119 
 1120         CM_SetField(ret, which, mptr, len);
 1121 
 1122         mptr += len + 1;                // advance to next field
 1123 
 1124     } while ((mptr < upper_bound) && (field_header != 'M'));
 1125     return (ret);
 1126 }
 1127 
 1128 
 1129 // Load a message from disk into memory.
 1130 // This is used by CtdlOutputMsg() and other fetch functions.
 1131 //
 1132 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
 1133 //       using the CM_Free(); function.
 1134 //
 1135 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
 1136     struct cdbdata *dmsgtext;
 1137     struct CtdlMessage *ret = NULL;
 1138 
 1139     syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
 1140     dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
 1141     if (dmsgtext == NULL) {
 1142         syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
 1143         return NULL;
 1144     }
 1145 
 1146     if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
 1147         syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
 1148         dmsgtext->ptr[dmsgtext->len - 1] = '\0';
 1149     }
 1150 
 1151     ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
 1152 
 1153     cdb_free(dmsgtext);
 1154 
 1155     if (ret == NULL) {
 1156         return NULL;
 1157     }
 1158 
 1159     // Always make sure there's something in the msg text field.  If
 1160     // it's NULL, the message text is most likely stored separately,
 1161     // so go ahead and fetch that.  Failing that, just set a dummy
 1162     // body so other code doesn't barf.
 1163     //
 1164     if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
 1165         dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
 1166         if (dmsgtext != NULL) {
 1167             CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
 1168             cdb_free(dmsgtext);
 1169         }
 1170     }
 1171     if (CM_IsEmpty(ret, eMesageText)) {
 1172         CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
 1173     }
 1174 
 1175     return (ret);
 1176 }
 1177 
 1178 
 1179 // Pre callback function for multipart/alternative
 1180 //
 1181 // NOTE: this differs from the standard behavior for a reason.  Normally when
 1182 //       displaying multipart/alternative you want to show the _last_ usable
 1183 //       format in the message.  Here we show the _first_ one, because it's
 1184 //       usually text/plain.  Since this set of functions is designed for text
 1185 //       output to non-MIME-aware clients, this is the desired behavior.
 1186 //
 1187 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
 1188         void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
 1189         char *cbid, void *cbuserdata)
 1190 {
 1191     struct ma_info *ma;
 1192     
 1193     ma = (struct ma_info *)cbuserdata;
 1194     syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype); 
 1195     if (!strcasecmp(cbtype, "multipart/alternative")) {
 1196         ++ma->is_ma;
 1197         ma->did_print = 0;
 1198     }
 1199     if (!strcasecmp(cbtype, "message/rfc822")) {
 1200         ++ma->freeze;
 1201     }
 1202 }
 1203 
 1204 
 1205 //
 1206 // Post callback function for multipart/alternative
 1207 //
 1208 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
 1209         void *content, char *cbtype, char *cbcharset, size_t length,
 1210         char *encoding, char *cbid, void *cbuserdata)
 1211 {
 1212     struct ma_info *ma;
 1213     
 1214     ma = (struct ma_info *)cbuserdata;
 1215     syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);    
 1216     if (!strcasecmp(cbtype, "multipart/alternative")) {
 1217         --ma->is_ma;
 1218         ma->did_print = 0;
 1219     }
 1220     if (!strcasecmp(cbtype, "message/rfc822")) {
 1221         --ma->freeze;
 1222     }
 1223 }
 1224 
 1225 
 1226 // Inline callback function for mime parser that wants to display text
 1227 //
 1228 void fixed_output(char *name, char *filename, char *partnum, char *disp,
 1229         void *content, char *cbtype, char *cbcharset, size_t length,
 1230         char *encoding, char *cbid, void *cbuserdata)
 1231 {
 1232     char *ptr;
 1233     char *wptr;
 1234     size_t wlen;
 1235     struct ma_info *ma;
 1236 
 1237     ma = (struct ma_info *)cbuserdata;
 1238 
 1239     syslog(LOG_DEBUG,
 1240         "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
 1241         partnum, filename, cbtype, (long)length
 1242     );
 1243 
 1244     // If we're in the middle of a multipart/alternative scope and
 1245     // we've already printed another section, skip this one.
 1246     //  
 1247     if ( (ma->is_ma) && (ma->did_print) ) {
 1248         syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
 1249         return;
 1250     }
 1251     ma->did_print = 1;
 1252 
 1253     if ( (!strcasecmp(cbtype, "text/plain")) 
 1254        || (IsEmptyStr(cbtype)) ) {
 1255         wptr = content;
 1256         if (length > 0) {
 1257             client_write(wptr, length);
 1258             if (wptr[length-1] != '\n') {
 1259                 cprintf("\n");
 1260             }
 1261         }
 1262         return;
 1263     }
 1264 
 1265     if (!strcasecmp(cbtype, "text/html")) {
 1266         ptr = html_to_ascii(content, length, 80);
 1267         wlen = strlen(ptr);
 1268         client_write(ptr, wlen);
 1269         if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
 1270             cprintf("\n");
 1271         }
 1272         free(ptr);
 1273         return;
 1274     }
 1275 
 1276     if (ma->use_fo_hooks) {
 1277         if (PerformFixedOutputHooks(cbtype, content, length)) {     // returns nonzero if it handled the part
 1278             return;
 1279         }
 1280     }
 1281 
 1282     if (strncasecmp(cbtype, "multipart/", 10)) {
 1283         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
 1284             partnum, filename, cbtype, (long)length);
 1285         return;
 1286     }
 1287 }
 1288 
 1289 
 1290 // The client is elegant and sophisticated and wants to be choosy about
 1291 // MIME content types, so figure out which multipart/alternative part
 1292 // we're going to send.
 1293 //
 1294 // We use a system of weights.  When we find a part that matches one of the
 1295 // MIME types we've declared as preferential, we can store it in ma->chosen_part
 1296 // and then set ma->chosen_pref to that MIME type's position in our preference
 1297 // list.  If we then hit another match, we only replace the first match if
 1298 // the preference value is lower.
 1299 //
 1300 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
 1301         void *content, char *cbtype, char *cbcharset, size_t length,
 1302         char *encoding, char *cbid, void *cbuserdata)
 1303 {
 1304     char buf[1024];
 1305     int i;
 1306     struct ma_info *ma;
 1307     
 1308     ma = (struct ma_info *)cbuserdata;
 1309 
 1310     for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
 1311         extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
 1312         if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
 1313             if (i < ma->chosen_pref) {
 1314                 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
 1315                 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
 1316                 ma->chosen_pref = i;
 1317             }
 1318         }
 1319     }
 1320 }
 1321 
 1322 
 1323 // Now that we've chosen our preferred part, output it.
 1324 //
 1325 void output_preferred(char *name, 
 1326               char *filename, 
 1327               char *partnum, 
 1328               char *disp,
 1329               void *content, 
 1330               char *cbtype, 
 1331               char *cbcharset, 
 1332               size_t length,
 1333               char *encoding, 
 1334               char *cbid, 
 1335               void *cbuserdata)
 1336 {
 1337     int i;
 1338     char buf[128];
 1339     int add_newline = 0;
 1340     char *text_content;
 1341     struct ma_info *ma;
 1342     char *decoded = NULL;
 1343     size_t bytes_decoded;
 1344     int rc = 0;
 1345 
 1346     ma = (struct ma_info *)cbuserdata;
 1347 
 1348     // This is not the MIME part you're looking for...
 1349     if (strcasecmp(partnum, ma->chosen_part)) return;
 1350 
 1351     // If the content-type of this part is in our preferred formats
 1352     // list, we can simply output it verbatim.
 1353     for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
 1354         extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
 1355         if (!strcasecmp(buf, cbtype)) {
 1356             /* Yeah!  Go!  W00t!! */
 1357             if (ma->dont_decode == 0) 
 1358                 rc = mime_decode_now (content, 
 1359                               length,
 1360                               encoding,
 1361                               &decoded,
 1362                               &bytes_decoded);
 1363             if (rc < 0)
 1364                 break; // Give us the chance, maybe theres another one.
 1365 
 1366             if (rc == 0) text_content = (char *)content;
 1367             else {
 1368                 text_content = decoded;
 1369                 length = bytes_decoded;
 1370             }
 1371 
 1372             if (text_content[length-1] != '\n') {
 1373                 ++add_newline;
 1374             }
 1375             cprintf("Content-type: %s", cbtype);
 1376             if (!IsEmptyStr(cbcharset)) {
 1377                 cprintf("; charset=%s", cbcharset);
 1378             }
 1379             cprintf("\nContent-length: %d\n",
 1380                 (int)(length + add_newline) );
 1381             if (!IsEmptyStr(encoding)) {
 1382                 cprintf("Content-transfer-encoding: %s\n", encoding);
 1383             }
 1384             else {
 1385                 cprintf("Content-transfer-encoding: 7bit\n");
 1386             }
 1387             cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
 1388             cprintf("\n");
 1389             if (client_write(text_content, length) == -1)
 1390             {
 1391                 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
 1392                 return;
 1393             }
 1394             if (add_newline) cprintf("\n");
 1395             if (decoded != NULL) free(decoded);
 1396             return;
 1397         }
 1398     }
 1399 
 1400     // No translations required or possible: output as text/plain
 1401     cprintf("Content-type: text/plain\n\n");
 1402     rc = 0;
 1403     if (ma->dont_decode == 0)
 1404         rc = mime_decode_now (content, 
 1405                       length,
 1406                       encoding,
 1407                       &decoded,
 1408                       &bytes_decoded);
 1409     if (rc < 0)
 1410         return; // Give us the chance, maybe theres another one.
 1411     
 1412     if (rc == 0) text_content = (char *)content;
 1413     else {
 1414         text_content = decoded;
 1415         length = bytes_decoded;
 1416     }
 1417 
 1418     fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
 1419     if (decoded != NULL) free(decoded);
 1420 }
 1421 
 1422 
 1423 struct encapmsg {
 1424     char desired_section[64];
 1425     char *msg;
 1426     size_t msglen;
 1427 };
 1428 
 1429 
 1430 // Callback function
 1431 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
 1432            void *content, char *cbtype, char *cbcharset, size_t length,
 1433            char *encoding, char *cbid, void *cbuserdata)
 1434 {
 1435     struct encapmsg *encap;
 1436 
 1437     encap = (struct encapmsg *)cbuserdata;
 1438 
 1439     // Only proceed if this is the desired section...
 1440     if (!strcasecmp(encap->desired_section, partnum)) {
 1441         encap->msglen = length;
 1442         encap->msg = malloc(length + 2);
 1443         memcpy(encap->msg, content, length);
 1444         return;
 1445     }
 1446 }
 1447 
 1448 
 1449 // Determine whether the specified message exists in the cached_msglist
 1450 // (This is a security check)
 1451 int check_cached_msglist(long msgnum) {
 1452 
 1453     // cases in which we skip the check
 1454     if (!CC) return om_ok;                      // not a session
 1455     if (CC->client_socket <= 0) return om_ok;           // not a client session
 1456     if (CC->cached_msglist == NULL) return om_access_denied;    // no msglist fetched
 1457     if (CC->cached_num_msgs == 0) return om_access_denied;      // nothing to check 
 1458 
 1459     // Do a binary search within the cached_msglist for the requested msgnum
 1460     int min = 0;
 1461     int max = (CC->cached_num_msgs - 1);
 1462 
 1463     while (max >= min) {
 1464         int middle = min + (max-min) / 2 ;
 1465         if (msgnum == CC->cached_msglist[middle]) {
 1466             return om_ok;
 1467         }
 1468         if (msgnum > CC->cached_msglist[middle]) {
 1469             min = middle + 1;
 1470         }
 1471         else {
 1472             max = middle - 1;
 1473         }
 1474     }
 1475 
 1476     return om_access_denied;
 1477 }
 1478 
 1479 
 1480 // Get a message off disk.  (returns om_* values found in msgbase.h)
 1481 int CtdlOutputMsg(long msg_num,     // message number (local) to fetch
 1482         int mode,       // how would you like that message?
 1483         int headers_only,   // eschew the message body?
 1484         int do_proto,       // do Citadel protocol responses?
 1485         int crlf,       // Use CRLF newlines instead of LF?
 1486         char *section,      // NULL or a message/rfc822 section
 1487         int flags,      // various flags; see msgbase.h
 1488         char **Author,
 1489         char **Address,
 1490         char **MessageID
 1491 ) {
 1492     struct CtdlMessage *TheMessage = NULL;
 1493     int retcode = CIT_OK;
 1494     struct encapmsg encap;
 1495     int r;
 1496 
 1497     syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)", 
 1498         msg_num, mode,
 1499         (section ? section : "<>")
 1500     );
 1501 
 1502     r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
 1503     if (r != om_ok) {
 1504         if (do_proto) {
 1505             if (r == om_not_logged_in) {
 1506                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
 1507             }
 1508             else {
 1509                 cprintf("%d An unknown error has occurred.\n", ERROR);
 1510             }
 1511         }
 1512         return(r);
 1513     }
 1514 
 1515     /*
 1516      * Check to make sure the message is actually IN this room
 1517      */
 1518     r = check_cached_msglist(msg_num);
 1519     if (r == om_access_denied) {
 1520         /* Not in the cache?  We get ONE shot to check it again. */
 1521         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
 1522         r = check_cached_msglist(msg_num);
 1523     }
 1524     if (r != om_ok) {
 1525         syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
 1526                msg_num, CC->room.QRname
 1527         );
 1528         if (do_proto) {
 1529             if (r == om_access_denied) {
 1530                 cprintf("%d message %ld was not found in this room\n",
 1531                     ERROR + HIGHER_ACCESS_REQUIRED,
 1532                     msg_num
 1533                 );
 1534             }
 1535         }
 1536         return(r);
 1537     }
 1538 
 1539     /*
 1540      * Fetch the message from disk.  If we're in HEADERS_FAST mode,
 1541      * request that we don't even bother loading the body into memory.
 1542      */
 1543     if (headers_only == HEADERS_FAST) {
 1544         TheMessage = CtdlFetchMessage(msg_num, 0);
 1545     }
 1546     else {
 1547         TheMessage = CtdlFetchMessage(msg_num, 1);
 1548     }
 1549 
 1550     if (TheMessage == NULL) {
 1551         if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
 1552             ERROR + MESSAGE_NOT_FOUND, msg_num);
 1553         return(om_no_such_msg);
 1554     }
 1555 
 1556     /* Here is the weird form of this command, to process only an
 1557      * encapsulated message/rfc822 section.
 1558      */
 1559     if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
 1560         memset(&encap, 0, sizeof encap);
 1561         safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
 1562         mime_parser(CM_RANGE(TheMessage, eMesageText),
 1563                 *extract_encapsulated_message,
 1564                 NULL, NULL, (void *)&encap, 0
 1565             );
 1566 
 1567         if ((Author != NULL) && (*Author == NULL))
 1568         {
 1569             long len;
 1570             CM_GetAsField(TheMessage, eAuthor, Author, &len);
 1571         }
 1572         if ((Address != NULL) && (*Address == NULL))
 1573         {   
 1574             long len;
 1575             CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
 1576         }
 1577         if ((MessageID != NULL) && (*MessageID == NULL))
 1578         {   
 1579             long len;
 1580             CM_GetAsField(TheMessage, emessageId, MessageID, &len);
 1581         }
 1582         CM_Free(TheMessage);
 1583         TheMessage = NULL;
 1584 
 1585         if (encap.msg) {
 1586             encap.msg[encap.msglen] = 0;
 1587             TheMessage = convert_internet_message(encap.msg);
 1588             encap.msg = NULL;   /* no free() here, TheMessage owns it now */
 1589 
 1590             /* Now we let it fall through to the bottom of this
 1591              * function, because TheMessage now contains the
 1592              * encapsulated message instead of the top-level
 1593              * message.  Isn't that neat?
 1594              */
 1595         }
 1596         else {
 1597             if (do_proto) {
 1598                 cprintf("%d msg %ld has no part %s\n",
 1599                     ERROR + MESSAGE_NOT_FOUND,
 1600                     msg_num,
 1601                     section);
 1602             }
 1603             retcode = om_no_such_msg;
 1604         }
 1605 
 1606     }
 1607 
 1608     /* Ok, output the message now */
 1609     if (retcode == CIT_OK)
 1610         retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
 1611     if ((Author != NULL) && (*Author == NULL))
 1612     {
 1613         long len;
 1614         CM_GetAsField(TheMessage, eAuthor, Author, &len);
 1615     }
 1616     if ((Address != NULL) && (*Address == NULL))
 1617     {   
 1618         long len;
 1619         CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
 1620     }
 1621     if ((MessageID != NULL) && (*MessageID == NULL))
 1622     {   
 1623         long len;
 1624         CM_GetAsField(TheMessage, emessageId, MessageID, &len);
 1625     }
 1626 
 1627     CM_Free(TheMessage);
 1628 
 1629     return(retcode);
 1630 }
 1631 
 1632 
 1633 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
 1634     int i;
 1635     char buf[SIZ];
 1636     char display_name[256];
 1637 
 1638     /* begin header processing loop for Citadel message format */
 1639     safestrncpy(display_name, "<unknown>", sizeof display_name);
 1640     if (!CM_IsEmpty(TheMessage, eAuthor)) {
 1641         strcpy(buf, TheMessage->cm_fields[eAuthor]);
 1642         if (TheMessage->cm_anon_type == MES_ANONONLY) {
 1643             safestrncpy(display_name, "****", sizeof display_name);
 1644         }
 1645         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
 1646             safestrncpy(display_name, "anonymous", sizeof display_name);
 1647         }
 1648         else {
 1649             safestrncpy(display_name, buf, sizeof display_name);
 1650         }
 1651         if ((is_room_aide())
 1652             && ((TheMessage->cm_anon_type == MES_ANONONLY)
 1653             || (TheMessage->cm_anon_type == MES_ANONOPT))) {
 1654             size_t tmp = strlen(display_name);
 1655             snprintf(&display_name[tmp],
 1656                  sizeof display_name - tmp,
 1657                  " [%s]", buf);
 1658         }
 1659     }
 1660 
 1661     /* Now spew the header fields in the order we like them. */
 1662     for (i=0; i< NDiskFields; ++i) {
 1663         eMsgField Field;
 1664         Field = FieldOrder[i];
 1665         if (Field != eMesageText) {
 1666             if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
 1667                 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
 1668                     sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
 1669                 }
 1670                 if (Field == eAuthor) {
 1671                     if (do_proto) {
 1672                         cprintf("%s=%s\n", msgkeys[Field], display_name);
 1673                     }
 1674                 }
 1675                 /* Masquerade display name if needed */
 1676                 else {
 1677                     if (do_proto) {
 1678                         cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
 1679                     }
 1680                 }
 1681                 /* Give the client a hint about whether the message originated locally */
 1682                 if (Field == erFc822Addr) {
 1683                     if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
 1684                         cprintf("locl=yes\n");              // message originated locally.
 1685                     }
 1686 
 1687 
 1688 
 1689                 }
 1690             }
 1691         }
 1692     }
 1693 }
 1694 
 1695 
 1696 void OutputRFC822MsgHeaders(
 1697     struct CtdlMessage *TheMessage,
 1698     int flags,      /* should the message be exported clean */
 1699     const char *nl, int nlen,
 1700     char *mid, long sizeof_mid,
 1701     char *suser, long sizeof_suser,
 1702     char *luser, long sizeof_luser,
 1703     char *fuser, long sizeof_fuser,
 1704     char *snode, long sizeof_snode)
 1705 {
 1706     char datestamp[100];
 1707     int subject_found = 0;
 1708     char buf[SIZ];
 1709     int i, j, k;
 1710     char *mptr = NULL;
 1711     char *mpptr = NULL;
 1712     char *hptr;
 1713 
 1714     for (i = 0; i < NDiskFields; ++i) {
 1715         if (TheMessage->cm_fields[FieldOrder[i]]) {
 1716             mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
 1717             switch (FieldOrder[i]) {
 1718             case eAuthor:
 1719                 safestrncpy(luser, mptr, sizeof_luser);
 1720                 safestrncpy(suser, mptr, sizeof_suser);
 1721                 break;
 1722             case eCarbonCopY:
 1723                 if ((flags & QP_EADDR) != 0) {
 1724                     mptr = qp_encode_email_addrs(mptr);
 1725                 }
 1726                 sanitize_truncated_recipient(mptr);
 1727                 cprintf("CC: %s%s", mptr, nl);
 1728                 break;
 1729             case eMessagePath:
 1730                 cprintf("Return-Path: %s%s", mptr, nl);
 1731                 break;
 1732             case eListID:
 1733                 cprintf("List-ID: %s%s", mptr, nl);
 1734                 break;
 1735             case eenVelopeTo:
 1736                 if ((flags & QP_EADDR) != 0) 
 1737                     mptr = qp_encode_email_addrs(mptr);
 1738                 hptr = mptr;
 1739                 while ((*hptr != '\0') && isspace(*hptr))
 1740                     hptr ++;
 1741                 if (!IsEmptyStr(hptr))
 1742                     cprintf("Envelope-To: %s%s", hptr, nl);
 1743                 break;
 1744             case eMsgSubject:
 1745                 cprintf("Subject: %s%s", mptr, nl);
 1746                 subject_found = 1;
 1747                 break;
 1748             case emessageId:
 1749                 safestrncpy(mid, mptr, sizeof_mid);
 1750                 break;
 1751             case erFc822Addr:
 1752                 safestrncpy(fuser, mptr, sizeof_fuser);
 1753                 break;
 1754             case eRecipient:
 1755                 if (haschar(mptr, '@') == 0) {
 1756                     sanitize_truncated_recipient(mptr);
 1757                     cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
 1758                     cprintf("%s", nl);
 1759                 }
 1760                 else {
 1761                     if ((flags & QP_EADDR) != 0) {
 1762                         mptr = qp_encode_email_addrs(mptr);
 1763                     }
 1764                     sanitize_truncated_recipient(mptr);
 1765                     cprintf("To: %s", mptr);
 1766                     cprintf("%s", nl);
 1767                 }
 1768                 break;
 1769             case eTimestamp:
 1770                 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
 1771                 cprintf("Date: %s%s", datestamp, nl);
 1772                 break;
 1773             case eWeferences:
 1774                 cprintf("References: ");
 1775                 k = num_tokens(mptr, '|');
 1776                 for (j=0; j<k; ++j) {
 1777                     extract_token(buf, mptr, j, '|', sizeof buf);
 1778                     cprintf("<%s>", buf);
 1779                     if (j == (k-1)) {
 1780                         cprintf("%s", nl);
 1781                     }
 1782                     else {
 1783                         cprintf(" ");
 1784                     }
 1785                 }
 1786                 break;
 1787             case eReplyTo:
 1788                 hptr = mptr;
 1789                 while ((*hptr != '\0') && isspace(*hptr))
 1790                     hptr ++;
 1791                 if (!IsEmptyStr(hptr))
 1792                     cprintf("Reply-To: %s%s", mptr, nl);
 1793                 break;
 1794 
 1795             case eExclusiveID:
 1796             case eJournal:
 1797             case eMesageText:
 1798             case eBig_message:
 1799             case eOriginalRoom:
 1800             case eErrorMsg:
 1801             case eSuppressIdx:
 1802             case eExtnotify:
 1803             case eVltMsgNum:
 1804                 /* these don't map to mime message headers. */
 1805                 break;
 1806             }
 1807             if (mptr != mpptr) {
 1808                 free (mptr);
 1809             }
 1810         }
 1811     }
 1812     if (subject_found == 0) {
 1813         cprintf("Subject: (no subject)%s", nl);
 1814     }
 1815 }
 1816 
 1817 
 1818 void Dump_RFC822HeadersBody(
 1819     struct CtdlMessage *TheMessage,
 1820     int headers_only,   /* eschew the message body? */
 1821     int flags,      /* should the bessage be exported clean? */
 1822     const char *nl, int nlen)
 1823 {
 1824     cit_uint8_t prev_ch;
 1825     int eoh = 0;
 1826     const char *StartOfText = StrBufNOTNULL;
 1827     char outbuf[1024];
 1828     int outlen = 0;
 1829     int nllen = strlen(nl);
 1830     char *mptr;
 1831     int lfSent = 0;
 1832 
 1833     mptr = TheMessage->cm_fields[eMesageText];
 1834 
 1835     prev_ch = '\0';
 1836     while (*mptr != '\0') {
 1837         if (*mptr == '\r') {
 1838             /* do nothing */
 1839         }
 1840         else {
 1841             if ((!eoh) &&
 1842                 (*mptr == '\n'))
 1843             {
 1844                 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
 1845                 if (!eoh)
 1846                     eoh = *(mptr+1) == '\n';
 1847                 if (eoh)
 1848                 {
 1849                     StartOfText = mptr;
 1850                     StartOfText = strchr(StartOfText, '\n');
 1851                     StartOfText = strchr(StartOfText, '\n');
 1852                 }
 1853             }
 1854             if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
 1855                 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
 1856                 ((headers_only != HEADERS_NONE) && 
 1857                  (headers_only != HEADERS_ONLY))
 1858             ) {
 1859                 if (*mptr == '\n') {
 1860                     memcpy(&outbuf[outlen], nl, nllen);
 1861                     outlen += nllen;
 1862                     outbuf[outlen] = '\0';
 1863                 }
 1864                 else {
 1865                     outbuf[outlen++] = *mptr;
 1866                 }
 1867             }
 1868         }
 1869         if (flags & ESC_DOT) {
 1870             if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
 1871                 outbuf[outlen++] = '.';
 1872             }
 1873             prev_ch = *mptr;
 1874         }
 1875         ++mptr;
 1876         if (outlen > 1000) {
 1877             if (client_write(outbuf, outlen) == -1) {
 1878                 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
 1879                 return;
 1880             }
 1881             lfSent =  (outbuf[outlen - 1] == '\n');
 1882             outlen = 0;
 1883         }
 1884     }
 1885     if (outlen > 0) {
 1886         client_write(outbuf, outlen);
 1887         lfSent =  (outbuf[outlen - 1] == '\n');
 1888     }
 1889     if (!lfSent)
 1890         client_write(nl, nlen);
 1891 }
 1892 
 1893 
 1894 /* If the format type on disk is 1 (fixed-format), then we want
 1895  * everything to be output completely literally ... regardless of
 1896  * what message transfer format is in use.
 1897  */
 1898 void DumpFormatFixed(
 1899     struct CtdlMessage *TheMessage,
 1900     int mode,       /* how would you like that message? */
 1901     const char *nl, int nllen)
 1902 {
 1903     cit_uint8_t ch;
 1904     char buf[SIZ];
 1905     int buflen;
 1906     int xlline = 0;
 1907     char *mptr;
 1908 
 1909     mptr = TheMessage->cm_fields[eMesageText];
 1910     
 1911     if (mode == MT_MIME) {
 1912         cprintf("Content-type: text/plain\n\n");
 1913     }
 1914     *buf = '\0';
 1915     buflen = 0;
 1916     while (ch = *mptr++, ch > 0) {
 1917         if (ch == '\n')
 1918             ch = '\r';
 1919 
 1920         if ((buflen > 250) && (!xlline)){
 1921             int tbuflen;
 1922             tbuflen = buflen;
 1923 
 1924             while ((buflen > 0) && 
 1925                    (!isspace(buf[buflen])))
 1926                 buflen --;
 1927             if (buflen == 0) {
 1928                 xlline = 1;
 1929             }
 1930             else {
 1931                 mptr -= tbuflen - buflen;
 1932                 buf[buflen] = '\0';
 1933                 ch = '\r';
 1934             }
 1935         }
 1936 
 1937         /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
 1938         if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
 1939             ch = '\r';
 1940         }
 1941 
 1942         if (ch == '\r') {
 1943             memcpy (&buf[buflen], nl, nllen);
 1944             buflen += nllen;
 1945             buf[buflen] = '\0';
 1946 
 1947             if (client_write(buf, buflen) == -1) {
 1948                 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
 1949                 return;
 1950             }
 1951             *buf = '\0';
 1952             buflen = 0;
 1953             xlline = 0;
 1954         } else {
 1955             buf[buflen] = ch;
 1956             buflen++;
 1957         }
 1958     }
 1959     buf[buflen] = '\0';
 1960     if (!IsEmptyStr(buf)) {
 1961         cprintf("%s%s", buf, nl);
 1962     }
 1963 }
 1964 
 1965 
 1966 /*
 1967  * Get a message off disk.  (returns om_* values found in msgbase.h)
 1968  */
 1969 int CtdlOutputPreLoadedMsg(
 1970         struct CtdlMessage *TheMessage,
 1971         int mode,       /* how would you like that message? */
 1972         int headers_only,   /* eschew the message body? */
 1973         int do_proto,       /* do Citadel protocol responses? */
 1974         int crlf,       /* Use CRLF newlines instead of LF? */
 1975         int flags       /* should the bessage be exported clean? */
 1976 ) {
 1977     int i;
 1978     const char *nl; /* newline string */
 1979     int nlen;
 1980     struct ma_info ma;
 1981 
 1982     /* Buffers needed for RFC822 translation.  These are all filled
 1983      * using functions that are bounds-checked, and therefore we can
 1984      * make them substantially smaller than SIZ.
 1985      */
 1986     char suser[1024];
 1987     char luser[1024];
 1988     char fuser[1024];
 1989     char snode[1024];
 1990     char mid[1024];
 1991 
 1992     syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
 1993            ((TheMessage == NULL) ? "NULL" : "not null"),
 1994            mode, headers_only, do_proto, crlf
 1995     );
 1996 
 1997     strcpy(mid, "unknown");
 1998     nl = (crlf ? "\r\n" : "\n");
 1999     nlen = crlf ? 2 : 1;
 2000 
 2001     if (!CM_IsValidMsg(TheMessage)) {
 2002         syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
 2003         return(om_no_such_msg);
 2004     }
 2005 
 2006     /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
 2007      * Pad it with spaces in order to avoid changing the RFC822 length of the message.
 2008      */
 2009     if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
 2010         memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
 2011     }
 2012         
 2013     /* Are we downloading a MIME component? */
 2014     if (mode == MT_DOWNLOAD) {
 2015         if (TheMessage->cm_format_type != FMT_RFC822) {
 2016             if (do_proto)
 2017                 cprintf("%d This is not a MIME message.\n",
 2018                 ERROR + ILLEGAL_VALUE);
 2019         } else if (CC->download_fp != NULL) {
 2020             if (do_proto) cprintf(
 2021                 "%d You already have a download open.\n",
 2022                 ERROR + RESOURCE_BUSY);
 2023         } else {
 2024             /* Parse the message text component */
 2025             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2026                     *mime_download, NULL, NULL, NULL, 0);
 2027             /* If there's no file open by this time, the requested
 2028              * section wasn't found, so print an error
 2029              */
 2030             if (CC->download_fp == NULL) {
 2031                 if (do_proto) cprintf(
 2032                     "%d Section %s not found.\n",
 2033                     ERROR + FILE_NOT_FOUND,
 2034                     CC->download_desired_section);
 2035             }
 2036         }
 2037         return((CC->download_fp != NULL) ? om_ok : om_mime_error);
 2038     }
 2039 
 2040     /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
 2041      * in a single server operation instead of opening a download file.
 2042      */
 2043     if (mode == MT_SPEW_SECTION) {
 2044         if (TheMessage->cm_format_type != FMT_RFC822) {
 2045             if (do_proto)
 2046                 cprintf("%d This is not a MIME message.\n",
 2047                 ERROR + ILLEGAL_VALUE);
 2048         } else {
 2049             /* Parse the message text component */
 2050             int found_it = 0;
 2051 
 2052             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2053                     *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
 2054             /* If section wasn't found, print an error
 2055              */
 2056             if (!found_it) {
 2057                 if (do_proto) cprintf(
 2058                     "%d Section %s not found.\n",
 2059                     ERROR + FILE_NOT_FOUND,
 2060                     CC->download_desired_section);
 2061             }
 2062         }
 2063         return((CC->download_fp != NULL) ? om_ok : om_mime_error);
 2064     }
 2065 
 2066     /* now for the user-mode message reading loops */
 2067     if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
 2068 
 2069     /* Does the caller want to skip the headers? */
 2070     if (headers_only == HEADERS_NONE) goto START_TEXT;
 2071 
 2072     /* Tell the client which format type we're using. */
 2073     if ( (mode == MT_CITADEL) && (do_proto) ) {
 2074         cprintf("type=%d\n", TheMessage->cm_format_type);   // Tell the client which format type we're using.
 2075     }
 2076 
 2077     /* nhdr=yes means that we're only displaying headers, no body */
 2078     if ( (TheMessage->cm_anon_type == MES_ANONONLY)
 2079        && ((mode == MT_CITADEL) || (mode == MT_MIME))
 2080        && (do_proto)
 2081        ) {
 2082         cprintf("nhdr=yes\n");
 2083     }
 2084 
 2085     if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
 2086         OutputCtdlMsgHeaders(TheMessage, do_proto);
 2087     }
 2088 
 2089     /* begin header processing loop for RFC822 transfer format */
 2090     strcpy(suser, "");
 2091     strcpy(luser, "");
 2092     strcpy(fuser, "");
 2093     strcpy(snode, "");
 2094     if (mode == MT_RFC822) 
 2095         OutputRFC822MsgHeaders(
 2096             TheMessage,
 2097             flags,
 2098             nl, nlen,
 2099             mid, sizeof(mid),
 2100             suser, sizeof(suser),
 2101             luser, sizeof(luser),
 2102             fuser, sizeof(fuser),
 2103             snode, sizeof(snode)
 2104             );
 2105 
 2106 
 2107     for (i=0; !IsEmptyStr(&suser[i]); ++i) {
 2108         suser[i] = tolower(suser[i]);
 2109         if (!isalnum(suser[i])) suser[i]='_';
 2110     }
 2111 
 2112     if (mode == MT_RFC822) {
 2113         /* Construct a fun message id */
 2114         cprintf("Message-ID: <%s", mid);
 2115         if (strchr(mid, '@')==NULL) {
 2116             cprintf("@%s", snode);
 2117         }
 2118         cprintf(">%s", nl);
 2119 
 2120         if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
 2121             cprintf("From: \"----\" <x@x.org>%s", nl);
 2122         }
 2123         else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
 2124             cprintf("From: \"anonymous\" <x@x.org>%s", nl);
 2125         }
 2126         else if (!IsEmptyStr(fuser)) {
 2127             cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
 2128         }
 2129         else {
 2130             cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
 2131         }
 2132 
 2133         /* Blank line signifying RFC822 end-of-headers */
 2134         if (TheMessage->cm_format_type != FMT_RFC822) {
 2135             cprintf("%s", nl);
 2136         }
 2137     }
 2138 
 2139     /* end header processing loop ... at this point, we're in the text */
 2140 START_TEXT:
 2141     if (headers_only == HEADERS_FAST) goto DONE;
 2142 
 2143     /* Tell the client about the MIME parts in this message */
 2144     if (TheMessage->cm_format_type == FMT_RFC822) {
 2145         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
 2146             memset(&ma, 0, sizeof(struct ma_info));
 2147             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2148                 (do_proto ? *list_this_part : NULL),
 2149                 (do_proto ? *list_this_pref : NULL),
 2150                 (do_proto ? *list_this_suff : NULL),
 2151                 (void *)&ma, 1);
 2152         }
 2153         else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
 2154             Dump_RFC822HeadersBody(
 2155                 TheMessage,
 2156                 headers_only,
 2157                 flags,
 2158                 nl, nlen);
 2159             goto DONE;
 2160         }
 2161     }
 2162 
 2163     if (headers_only == HEADERS_ONLY) {
 2164         goto DONE;
 2165     }
 2166 
 2167     /* signify start of msg text */
 2168     if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
 2169         if (do_proto) cprintf("text\n");
 2170     }
 2171 
 2172     if (TheMessage->cm_format_type == FMT_FIXED) 
 2173         DumpFormatFixed(
 2174             TheMessage,
 2175             mode,       /* how would you like that message? */
 2176             nl, nlen);
 2177 
 2178     /* If the message on disk is format 0 (Citadel vari-format), we
 2179      * output using the formatter at 80 columns.  This is the final output
 2180      * form if the transfer format is RFC822, but if the transfer format
 2181      * is Citadel proprietary, it'll still work, because the indentation
 2182      * for new paragraphs is correct and the client will reformat the
 2183      * message to the reader's screen width.
 2184      */
 2185     if (TheMessage->cm_format_type == FMT_CITADEL) {
 2186         if (mode == MT_MIME) {
 2187             cprintf("Content-type: text/x-citadel-variformat\n\n");
 2188         }
 2189         memfmout(TheMessage->cm_fields[eMesageText], nl);
 2190     }
 2191 
 2192     /* If the message on disk is format 4 (MIME), we've gotta hand it
 2193      * off to the MIME parser.  The client has already been told that
 2194      * this message is format 1 (fixed format), so the callback function
 2195      * we use will display those parts as-is.
 2196      */
 2197     if (TheMessage->cm_format_type == FMT_RFC822) {
 2198         memset(&ma, 0, sizeof(struct ma_info));
 2199 
 2200         if (mode == MT_MIME) {
 2201             ma.use_fo_hooks = 0;
 2202             strcpy(ma.chosen_part, "1");
 2203             ma.chosen_pref = 9999;
 2204             ma.dont_decode = CC->msg4_dont_decode;
 2205             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2206                     *choose_preferred, *fixed_output_pre,
 2207                     *fixed_output_post, (void *)&ma, 1);
 2208             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2209                     *output_preferred, NULL, NULL, (void *)&ma, 1);
 2210         }
 2211         else {
 2212             ma.use_fo_hooks = 1;
 2213             mime_parser(CM_RANGE(TheMessage, eMesageText),
 2214                     *fixed_output, *fixed_output_pre,
 2215                     *fixed_output_post, (void *)&ma, 0);
 2216         }
 2217 
 2218     }
 2219 
 2220 DONE:   /* now we're done */
 2221     if (do_proto) cprintf("000\n");
 2222     return(om_ok);
 2223 }
 2224 
 2225 /*
 2226  * Save one or more message pointers into a specified room
 2227  * (Returns 0 for success, nonzero for failure)
 2228  * roomname may be NULL to use the current room
 2229  *
 2230  * Note that the 'supplied_msg' field may be set to NULL, in which case
 2231  * the message will be fetched from disk, by number, if we need to perform
 2232  * replication checks.  This adds an additional database read, so if the
 2233  * caller already has the message in memory then it should be supplied.  (Obviously
 2234  * this mode of operation only works if we're saving a single message.)
 2235  */
 2236 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
 2237             int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
 2238 ) {
 2239     int i, j, unique;
 2240     char hold_rm[ROOMNAMELEN];
 2241     struct cdbdata *cdbfr;
 2242     int num_msgs;
 2243     long *msglist;
 2244     long highest_msg = 0L;
 2245 
 2246     long msgid = 0;
 2247     struct CtdlMessage *msg = NULL;
 2248 
 2249     long *msgs_to_be_merged = NULL;
 2250     int num_msgs_to_be_merged = 0;
 2251 
 2252     syslog(LOG_DEBUG,
 2253         "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
 2254         roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
 2255     );
 2256 
 2257     strcpy(hold_rm, CC->room.QRname);
 2258 
 2259     /* Sanity checks */
 2260     if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
 2261     if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
 2262     if (num_newmsgs > 1) supplied_msg = NULL;
 2263 
 2264     /* Now the regular stuff */
 2265     if (CtdlGetRoomLock(&CC->room,
 2266        ((roomname != NULL) ? roomname : CC->room.QRname) )
 2267        != 0) {
 2268         syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
 2269         return(ERROR + ROOM_NOT_FOUND);
 2270     }
 2271 
 2272 
 2273     msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
 2274     num_msgs_to_be_merged = 0;
 2275 
 2276 
 2277     cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
 2278     if (cdbfr == NULL) {
 2279         msglist = NULL;
 2280         num_msgs = 0;
 2281     } else {
 2282         msglist = (long *) cdbfr->ptr;
 2283         cdbfr->ptr = NULL;  /* CtdlSaveMsgPointerInRoom() now owns this memory */
 2284         num_msgs = cdbfr->len / sizeof(long);
 2285         cdb_free(cdbfr);
 2286     }
 2287 
 2288 
 2289     /* Create a list of msgid's which were supplied by the caller, but do
 2290      * not already exist in the target room.  It is absolutely taboo to
 2291      * have more than one reference to the same message in a room.
 2292      */
 2293     for (i=0; i<num_newmsgs; ++i) {
 2294         unique = 1;
 2295         if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
 2296             if (msglist[j] == newmsgidlist[i]) {
 2297                 unique = 0;
 2298             }
 2299         }
 2300         if (unique) {
 2301             msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
 2302         }
 2303     }
 2304 
 2305     syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
 2306 
 2307     /*
 2308      * Now merge the new messages
 2309      */
 2310     msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
 2311     if (msglist == NULL) {
 2312         syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
 2313         free(msgs_to_be_merged);
 2314         return (ERROR + INTERNAL_ERROR);
 2315     }
 2316     memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
 2317     num_msgs += num_msgs_to_be_merged;
 2318 
 2319     /* Sort the message list, so all the msgid's are in order */
 2320     num_msgs = sort_msglist(msglist, num_msgs);
 2321 
 2322     /* Determine the highest message number */
 2323     highest_msg = msglist[num_msgs - 1];
 2324 
 2325     /* Write it back to disk. */
 2326     cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
 2327           msglist, (int)(num_msgs * sizeof(long)));
 2328 
 2329     /* Free up the memory we used. */
 2330     free(msglist);
 2331 
 2332     /* Update the highest-message pointer and unlock the room. */
 2333     CC->room.QRhighest = highest_msg;
 2334     CtdlPutRoomLock(&CC->room);
 2335 
 2336     /* Perform replication checks if necessary */
 2337     if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
 2338         syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
 2339 
 2340         for (i=0; i<num_msgs_to_be_merged; ++i) {
 2341             msgid = msgs_to_be_merged[i];
 2342     
 2343             if (supplied_msg != NULL) {
 2344                 msg = supplied_msg;
 2345             }
 2346             else {
 2347                 msg = CtdlFetchMessage(msgid, 0);
 2348             }
 2349     
 2350             if (msg != NULL) {
 2351                 ReplicationChecks(msg);
 2352         
 2353                 /* If the message has an Exclusive ID, index that... */
 2354                 if (!CM_IsEmpty(msg, eExclusiveID)) {
 2355                     index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
 2356                 }
 2357 
 2358                 /* Free up the memory we may have allocated */
 2359                 if (msg != supplied_msg) {
 2360                     CM_Free(msg);
 2361                 }
 2362             }
 2363     
 2364         }
 2365     }
 2366 
 2367     else {
 2368         syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
 2369     }
 2370 
 2371     /* Submit this room for processing by hooks */
 2372     int total_roomhook_errors = PerformRoomHooks(&CC->room);
 2373     if (total_roomhook_errors) {
 2374         syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
 2375     }
 2376 
 2377     /* Go back to the room we were in before we wandered here... */
 2378     CtdlGetRoom(&CC->room, hold_rm);
 2379 
 2380     /* Bump the reference count for all messages which were merged */
 2381     if (!suppress_refcount_adj) {
 2382         AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
 2383     }
 2384 
 2385     /* Free up memory... */
 2386     if (msgs_to_be_merged != NULL) {
 2387         free(msgs_to_be_merged);
 2388     }
 2389 
 2390     /* Return success. */
 2391     return (0);
 2392 }
 2393 
 2394 
 2395 /*
 2396  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
 2397  * a single message.
 2398  */
 2399 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
 2400                  int do_repl_check, struct CtdlMessage *supplied_msg)
 2401 {
 2402     return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
 2403 }
 2404 
 2405 
 2406 /*
 2407  * Message base operation to save a new message to the message store
 2408  * (returns new message number)
 2409  *
 2410  * This is the back end for CtdlSubmitMsg() and should not be directly
 2411  * called by server-side modules.
 2412  *
 2413  */
 2414 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
 2415     long retval;
 2416     struct ser_ret smr;
 2417     int is_bigmsg = 0;
 2418     char *holdM = NULL;
 2419     long holdMLen = 0;
 2420 
 2421     /*
 2422      * If the message is big, set its body aside for storage elsewhere
 2423      * and we hide the message body from the serializer
 2424      */
 2425     if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
 2426         is_bigmsg = 1;
 2427         holdM = msg->cm_fields[eMesageText];
 2428         msg->cm_fields[eMesageText] = NULL;
 2429         holdMLen = msg->cm_lengths[eMesageText];
 2430         msg->cm_lengths[eMesageText] = 0;
 2431     }
 2432 
 2433     /* Serialize our data structure for storage in the database */  
 2434     CtdlSerializeMessage(&smr, msg);
 2435 
 2436     if (is_bigmsg) {
 2437         /* put the message body back into the message */
 2438         msg->cm_fields[eMesageText] = holdM;
 2439         msg->cm_lengths[eMesageText] = holdMLen;
 2440     }
 2441 
 2442     if (smr.len == 0) {
 2443         if (Reply) {
 2444             cprintf("%d Unable to serialize message\n",
 2445                 ERROR + INTERNAL_ERROR);
 2446         }
 2447         else {
 2448             syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
 2449 
 2450         }
 2451         return (-1L);
 2452     }
 2453 
 2454     /* Write our little bundle of joy into the message base */
 2455     retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
 2456     if (retval < 0) {
 2457         syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
 2458     }
 2459     else {
 2460         if (is_bigmsg) {
 2461             retval = cdb_store(CDB_BIGMSGS,
 2462                        &msgid,
 2463                        (int)sizeof(long),
 2464                        holdM,
 2465                        (holdMLen + 1)
 2466                 );
 2467             if (retval < 0) {
 2468                 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
 2469             }
 2470         }
 2471     }
 2472 
 2473     /* Free the memory we used for the serialized message */
 2474     free(smr.ser);
 2475 
 2476     return(retval);
 2477 }
 2478 
 2479 
 2480 long send_message(struct CtdlMessage *msg) {
 2481     long newmsgid;
 2482     long retval;
 2483     char msgidbuf[256];
 2484     long msgidbuflen;
 2485 
 2486     /* Get a new message number */
 2487     newmsgid = get_new_message_number();
 2488 
 2489     /* Generate an ID if we don't have one already */
 2490     if (CM_IsEmpty(msg, emessageId)) {
 2491         msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
 2492                        (long unsigned int) time(NULL),
 2493                        (long unsigned int) newmsgid,
 2494                        CtdlGetConfigStr("c_fqdn")
 2495             );
 2496 
 2497         CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
 2498     }
 2499 
 2500     retval = CtdlSaveThisMessage(msg, newmsgid, 1);
 2501 
 2502     if (retval == 0) {
 2503         retval = newmsgid;
 2504     }
 2505 
 2506     /* Return the *local* message ID to the caller
 2507      * (even if we're storing an incoming network message)
 2508      */
 2509     return(retval);
 2510 }
 2511 
 2512 
 2513 /*
 2514  * Serialize a struct CtdlMessage into the format used on disk.
 2515  * 
 2516  * This function loads up a "struct ser_ret" (defined in server.h) which
 2517  * contains the length of the serialized message and a pointer to the
 2518  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
 2519  */
 2520 void CtdlSerializeMessage(struct ser_ret *ret,      /* return values */
 2521               struct CtdlMessage *msg)  /* unserialized msg */
 2522 {
 2523     size_t wlen;
 2524     int i;
 2525 
 2526     /*
 2527      * Check for valid message format
 2528      */
 2529     if (CM_IsValidMsg(msg) == 0) {
 2530         syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
 2531         ret->len = 0;
 2532         ret->ser = NULL;
 2533         return;
 2534     }
 2535 
 2536     ret->len = 3;
 2537     for (i=0; i < NDiskFields; ++i)
 2538         if (msg->cm_fields[FieldOrder[i]] != NULL)
 2539             ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
 2540 
 2541     ret->ser = malloc(ret->len);
 2542     if (ret->ser == NULL) {
 2543         syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
 2544         ret->len = 0;
 2545         ret->ser = NULL;
 2546         return;
 2547     }
 2548 
 2549     ret->ser[0] = 0xFF;
 2550     ret->ser[1] = msg->cm_anon_type;
 2551     ret->ser[2] = msg->cm_format_type;
 2552     wlen = 3;
 2553 
 2554     for (i=0; i < NDiskFields; ++i) {
 2555         if (msg->cm_fields[FieldOrder[i]] != NULL) {
 2556             ret->ser[wlen++] = (char)FieldOrder[i];
 2557 
 2558             memcpy(&ret->ser[wlen],
 2559                    msg->cm_fields[FieldOrder[i]],
 2560                    msg->cm_lengths[FieldOrder[i]] + 1);
 2561 
 2562             wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
 2563         }
 2564     }
 2565 
 2566     if (ret->len != wlen) {
 2567         syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
 2568     }
 2569 
 2570     return;
 2571 }
 2572 
 2573 
 2574 /*
 2575  * Check to see if any messages already exist in the current room which
 2576  * carry the same Exclusive ID as this one.  If any are found, delete them.
 2577  */
 2578 void ReplicationChecks(struct CtdlMessage *msg) {
 2579     long old_msgnum = (-1L);
 2580 
 2581     if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
 2582 
 2583     syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
 2584 
 2585     /* No exclusive id?  Don't do anything. */
 2586     if (msg == NULL) return;
 2587     if (CM_IsEmpty(msg, eExclusiveID)) return;
 2588 
 2589     /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
 2590       msg->cm_fields[eExclusiveID], CC->room.QRname);*/
 2591 
 2592     old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
 2593     if (old_msgnum > 0L) {
 2594         syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
 2595         CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
 2596     }
 2597 }
 2598 
 2599 
 2600 /*
 2601  * Save a message to disk and submit it into the delivery system.
 2602  */
 2603 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
 2604            struct recptypes *recps, /* recipients (if mail) */
 2605            const char *force        /* force a particular room? */
 2606 ) {
 2607     char hold_rm[ROOMNAMELEN];
 2608     char actual_rm[ROOMNAMELEN];
 2609     char force_room[ROOMNAMELEN];
 2610     char content_type[SIZ];         /* We have to learn this */
 2611     char recipient[SIZ];
 2612     char bounce_to[1024];
 2613     const char *room;
 2614     long newmsgid;
 2615     const char *mptr = NULL;
 2616     struct ctdluser userbuf;
 2617     int a, i;
 2618     struct MetaData smi;
 2619     char *collected_addresses = NULL;
 2620     struct addresses_to_be_filed *aptr = NULL;
 2621     StrBuf *saved_rfc822_version = NULL;
 2622     int qualified_for_journaling = 0;
 2623 
 2624     syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
 2625     if (CM_IsValidMsg(msg) == 0) return(-1);    /* self check */
 2626 
 2627     /* If this message has no timestamp, we take the liberty of
 2628      * giving it one, right now.
 2629      */
 2630     if (CM_IsEmpty(msg, eTimestamp)) {
 2631         CM_SetFieldLONG(msg, eTimestamp, time(NULL));
 2632     }
 2633 
 2634     /* If this message has no path, we generate one.
 2635      */
 2636     if (CM_IsEmpty(msg, eMessagePath)) {
 2637         if (!CM_IsEmpty(msg, eAuthor)) {
 2638             CM_CopyField(msg, eMessagePath, eAuthor);
 2639             for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
 2640                 if (isspace(msg->cm_fields[eMessagePath][a])) {
 2641                     msg->cm_fields[eMessagePath][a] = ' ';
 2642                 }
 2643             }
 2644         }
 2645         else {
 2646             CM_SetField(msg, eMessagePath, HKEY("unknown"));
 2647         }
 2648     }
 2649 
 2650     if (force == NULL) {
 2651         force_room[0] = '\0';
 2652     }
 2653     else {
 2654         strcpy(force_room, force);
 2655     }
 2656 
 2657     /* Learn about what's inside, because it's what's inside that counts */
 2658     if (CM_IsEmpty(msg, eMesageText)) {
 2659         syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
 2660         return(-2);
 2661     }
 2662 
 2663     switch (msg->cm_format_type) {
 2664     case 0:
 2665         strcpy(content_type, "text/x-citadel-variformat");
 2666         break;
 2667     case 1:
 2668         strcpy(content_type, "text/plain");
 2669         break;
 2670     case 4:
 2671         strcpy(content_type, "text/plain");
 2672         mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
 2673         if (mptr != NULL) {
 2674             char *aptr;
 2675             safestrncpy(content_type, &mptr[13], sizeof content_type);
 2676             striplt(content_type);
 2677             aptr = content_type;
 2678             while (!IsEmptyStr(aptr)) {
 2679                 if ((*aptr == ';')
 2680                     || (*aptr == ' ')
 2681                     || (*aptr == 13)
 2682                     || (*aptr == 10)) {
 2683                     *aptr = 0;
 2684                 }
 2685                 else aptr++;
 2686             }
 2687         }
 2688     }
 2689 
 2690     /* Goto the correct room */
 2691     room = (recps) ? CC->room.QRname : SENTITEMS;
 2692     syslog(LOG_DEBUG, "msgbase: selected room %s", room);
 2693     strcpy(hold_rm, CC->room.QRname);
 2694     strcpy(actual_rm, CC->room.QRname);
 2695     if (recps != NULL) {
 2696         strcpy(actual_rm, SENTITEMS);
 2697     }
 2698 
 2699     /* If the user is a twit, move to the twit room for posting */
 2700     if (TWITDETECT) {
 2701         if (CC->user.axlevel == AxProbU) {
 2702             strcpy(hold_rm, actual_rm);
 2703             strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
 2704             syslog(LOG_DEBUG, "msgbase: diverting to twit room");
 2705         }
 2706     }
 2707 
 2708     /* ...or if this message is destined for Aide> then go there. */
 2709     if (!IsEmptyStr(force_room)) {
 2710         strcpy(actual_rm, force_room);
 2711     }
 2712 
 2713     syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
 2714     if (strcasecmp(actual_rm, CC->room.QRname)) {
 2715         /* CtdlGetRoom(&CC->room, actual_rm); */
 2716         CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
 2717     }
 2718 
 2719     /*
 2720      * If this message has no O (room) field, generate one.
 2721      */
 2722     if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
 2723         CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
 2724     }
 2725 
 2726     /* Perform "before save" hooks (aborting if any return nonzero) */
 2727     syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
 2728     if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
 2729 
 2730     /*
 2731      * If this message has an Exclusive ID, and the room is replication
 2732      * checking enabled, then do replication checks.
 2733      */
 2734     if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
 2735         ReplicationChecks(msg);
 2736     }
 2737 
 2738     /* Save it to disk */
 2739     syslog(LOG_DEBUG, "msgbase: saving to disk");
 2740     newmsgid = send_message(msg);
 2741     if (newmsgid <= 0L) return(-5);
 2742 
 2743     /* Write a supplemental message info record.  This doesn't have to
 2744      * be a critical section because nobody else knows about this message
 2745      * yet.
 2746      */
 2747     syslog(LOG_DEBUG, "msgbase: creating metadata record");
 2748     memset(&smi, 0, sizeof(struct MetaData));
 2749     smi.meta_msgnum = newmsgid;
 2750     smi.meta_refcount = 0;
 2751     safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
 2752 
 2753     /*
 2754      * Measure how big this message will be when rendered as RFC822.
 2755      * We do this for two reasons:
 2756      * 1. We need the RFC822 length for the new metadata record, so the
 2757      *    POP and IMAP services don't have to calculate message lengths
 2758      *    while the user is waiting (multiplied by potentially hundreds
 2759      *    or thousands of messages).
 2760      * 2. If journaling is enabled, we will need an RFC822 version of the
 2761      *    message to attach to the journalized copy.
 2762      */
 2763     if (CC->redirect_buffer != NULL) {
 2764         syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
 2765         abort();
 2766     }
 2767     CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
 2768     CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
 2769     smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
 2770     saved_rfc822_version = CC->redirect_buffer;
 2771     CC->redirect_buffer = NULL;
 2772 
 2773     PutMetaData(&smi);
 2774 
 2775     /* Now figure out where to store the pointers */
 2776     syslog(LOG_DEBUG, "msgbase: storing pointers");
 2777 
 2778     /* If this is being done by the networker delivering a private
 2779      * message, we want to BYPASS saving the sender's copy (because there
 2780      * is no local sender; it would otherwise go to the Trashcan).
 2781      */
 2782     if ((!CC->internal_pgm) || (recps == NULL)) {
 2783         if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
 2784             syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
 2785             CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
 2786         }
 2787     }
 2788 
 2789     /* For internet mail, drop a copy in the outbound queue room */
 2790     if ((recps != NULL) && (recps->num_internet > 0)) {
 2791         CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
 2792     }
 2793 
 2794     /* If other rooms are specified, drop them there too. */
 2795     if ((recps != NULL) && (recps->num_room > 0)) {
 2796         for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
 2797             extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
 2798             syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
 2799             CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
 2800         }
 2801     }
 2802 
 2803     /* Bump this user's messages posted counter. */
 2804     syslog(LOG_DEBUG, "msgbase: updating user");
 2805     CtdlLockGetCurrentUser();
 2806     CC->user.posted = CC->user.posted + 1;
 2807     CtdlPutCurrentUserLock();
 2808 
 2809     /* Decide where bounces need to be delivered */
 2810     if ((recps != NULL) && (recps->bounce_to == NULL)) {
 2811         if (CC->logged_in) {
 2812             strcpy(bounce_to, CC->user.fullname);
 2813         }
 2814         else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
 2815             strcpy(bounce_to, msg->cm_fields[eAuthor]);
 2816         }
 2817         recps->bounce_to = bounce_to;
 2818     }
 2819         
 2820     CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
 2821 
 2822     /* If this is private, local mail, make a copy in the
 2823      * recipient's mailbox and bump the reference count.
 2824      */
 2825     if ((recps != NULL) && (recps->num_local > 0)) {
 2826         char *pch;
 2827         int ntokens;
 2828 
 2829         pch = recps->recp_local;
 2830         recps->recp_local = recipient;
 2831         ntokens = num_tokens(pch, '|');
 2832         for (i=0; i<ntokens; ++i) {
 2833             extract_token(recipient, pch, i, '|', sizeof recipient);
 2834             syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
 2835             if (CtdlGetUser(&userbuf, recipient) == 0) {
 2836                 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
 2837                 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
 2838                 CtdlBumpNewMailCounter(userbuf.usernum);
 2839                 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
 2840             }
 2841             else {
 2842                 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
 2843                 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
 2844             }
 2845         }
 2846         recps->recp_local = pch;
 2847     }
 2848 
 2849     /* Perform "after save" hooks */
 2850     syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
 2851 
 2852     PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
 2853     CM_FlushField(msg, eVltMsgNum);
 2854 
 2855     /* Go back to the room we started from */
 2856     syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
 2857     if (strcasecmp(hold_rm, CC->room.QRname)) {
 2858         CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
 2859     }
 2860 
 2861     /*
 2862      * Any addresses to harvest for someone's address book?
 2863      */
 2864     if ( (CC->logged_in) && (recps != NULL) ) {
 2865         collected_addresses = harvest_collected_addresses(msg);
 2866     }
 2867 
 2868     if (collected_addresses != NULL) {
 2869         aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
 2870         CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
 2871         aptr->roomname = strdup(actual_rm);
 2872         aptr->collected_addresses = collected_addresses;
 2873         begin_critical_section(S_ATBF);
 2874         aptr->next = atbf;
 2875         atbf = aptr;
 2876         end_critical_section(S_ATBF);
 2877     }
 2878 
 2879     /*
 2880      * Determine whether this message qualifies for journaling.
 2881      */
 2882     if (!CM_IsEmpty(msg, eJournal)) {
 2883         qualified_for_journaling = 0;
 2884     }
 2885     else {
 2886         if (recps == NULL) {
 2887             qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
 2888         }
 2889         else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
 2890             qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
 2891         }
 2892         else {
 2893             qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
 2894         }
 2895     }
 2896 
 2897     /*
 2898      * Do we have to perform journaling?  If so, hand off the saved
 2899      * RFC822 version will be handed off to the journaler for background
 2900      * submit.  Otherwise, we have to free the memory ourselves.
 2901      */
 2902     if (saved_rfc822_version != NULL) {
 2903         if (qualified_for_journaling) {
 2904             JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
 2905         }
 2906         else {
 2907             FreeStrBuf(&saved_rfc822_version);
 2908         }
 2909     }
 2910 
 2911     if ((recps != NULL) && (recps->bounce_to == bounce_to))
 2912         recps->bounce_to = NULL;
 2913 
 2914     /* Done. */
 2915     return(newmsgid);
 2916 }
 2917 
 2918 
 2919 /*
 2920  * Convenience function for generating small administrative messages.
 2921  */
 2922 long quickie_message(const char *from,
 2923              const char *fromaddr,
 2924              const char *to,
 2925              char *room,
 2926              const char *text, 
 2927              int format_type,
 2928              const char *subject)
 2929 {
 2930     struct CtdlMessage *msg;
 2931     struct recptypes *recp = NULL;
 2932 
 2933     msg = malloc(sizeof(struct CtdlMessage));
 2934     memset(msg, 0, sizeof(struct CtdlMessage));
 2935     msg->cm_magic = CTDLMESSAGE_MAGIC;
 2936     msg->cm_anon_type = MES_NORMAL;
 2937     msg->cm_format_type = format_type;
 2938 
 2939     if (!IsEmptyStr(from)) {
 2940         CM_SetField(msg, eAuthor, from, -1);
 2941     }
 2942     else if (!IsEmptyStr(fromaddr)) {
 2943         char *pAt;
 2944         CM_SetField(msg, eAuthor, fromaddr, -1);
 2945         pAt = strchr(msg->cm_fields[eAuthor], '@');
 2946         if (pAt != NULL) {
 2947             CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
 2948         }
 2949     }
 2950     else {
 2951         msg->cm_fields[eAuthor] = strdup("Citadel");
 2952     }
 2953 
 2954     if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
 2955     if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
 2956     if (!IsEmptyStr(to)) {
 2957         CM_SetField(msg, eRecipient, to, -1);
 2958         recp = validate_recipients(to, NULL, 0);
 2959     }
 2960     if (!IsEmptyStr(subject)) {
 2961         CM_SetField(msg, eMsgSubject, subject, -1);
 2962     }
 2963     if (!IsEmptyStr(text)) {
 2964         CM_SetField(msg, eMesageText, text, -1);
 2965     }
 2966 
 2967     long msgnum = CtdlSubmitMsg(msg, recp, room);
 2968     CM_Free(msg);
 2969     if (recp != NULL) free_recipients(recp);
 2970     return msgnum;
 2971 }
 2972 
 2973 
 2974 /*
 2975  * Back end function used by CtdlMakeMessage() and similar functions
 2976  */
 2977 StrBuf *CtdlReadMessageBodyBuf(char *terminator,    /* token signalling EOT */
 2978                    long tlen,
 2979                    size_t maxlen,       /* maximum message length */
 2980                    StrBuf *exist,       /* if non-null, append to it;
 2981                                exist is ALWAYS freed  */
 2982                    int crlf         /* CRLF newlines instead of LF */
 2983 ) {
 2984     StrBuf *Message;
 2985     StrBuf *LineBuf;
 2986     int flushing = 0;
 2987     int finished = 0;
 2988     int dotdot = 0;
 2989 
 2990     LineBuf = NewStrBufPlain(NULL, SIZ);
 2991     if (exist == NULL) {
 2992         Message = NewStrBufPlain(NULL, 4 * SIZ);
 2993     }
 2994     else {
 2995         Message = NewStrBufDup(exist);
 2996     }
 2997 
 2998     /* Do we need to change leading ".." to "." for SMTP escaping? */
 2999     if ((tlen == 1) && (*terminator == '.')) {
 3000         dotdot = 1;
 3001     }
 3002 
 3003     /* read in the lines of message text one by one */
 3004     do {
 3005         if (CtdlClientGetLine(LineBuf) < 0) {
 3006             finished = 1;
 3007         }
 3008         if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
 3009             finished = 1;
 3010         }
 3011         if ( (!flushing) && (!finished) ) {
 3012             if (crlf) {
 3013                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
 3014             }
 3015             else {
 3016                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
 3017             }
 3018             
 3019             /* Unescape SMTP-style input of two dots at the beginning of the line */
 3020             if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
 3021                 StrBufCutLeft(LineBuf, 1);
 3022             }
 3023             StrBufAppendBuf(Message, LineBuf, 0);
 3024         }
 3025 
 3026         /* if we've hit the max msg length, flush the rest */
 3027         if (StrLength(Message) >= maxlen) flushing = 1;
 3028 
 3029     } while (!finished);
 3030     FreeStrBuf(&LineBuf);
 3031     return Message;
 3032 }
 3033 
 3034 
 3035 /*
 3036  * Back end function used by CtdlMakeMessage() and similar functions
 3037  */
 3038 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
 3039               long tlen,
 3040               size_t maxlen,        /* maximum message length */
 3041               StrBuf *exist,        /* if non-null, append to it;
 3042                            exist is ALWAYS freed  */
 3043               int crlf      /* CRLF newlines instead of LF */
 3044     ) 
 3045 {
 3046     StrBuf *Message;
 3047 
 3048     Message = CtdlReadMessageBodyBuf(terminator,
 3049                      tlen,
 3050                      maxlen,
 3051                      exist,
 3052                      crlf
 3053     );
 3054     if (Message == NULL)
 3055         return NULL;
 3056     else
 3057         return SmashStrBuf(&Message);
 3058 }
 3059 
 3060 
 3061 struct CtdlMessage *CtdlMakeMessage(
 3062         struct ctdluser *author,        /* author's user structure */
 3063         char *recipient,                /* NULL if it's not mail */
 3064         char *recp_cc,                  /* NULL if it's not mail */
 3065         char *room,                     /* room where it's going */
 3066         int type,                       /* see MES_ types in header file */
 3067         int format_type,                /* variformat, plain text, MIME... */
 3068         char *fake_name,                /* who we're masquerading as */
 3069     char *my_email,         /* which of my email addresses to use (empty is ok) */
 3070         char *subject,                  /* Subject (optional) */
 3071     char *supplied_euid,        /* ...or NULL if this is irrelevant */
 3072         char *preformatted_text,        /* ...or NULL to read text from client */
 3073     char *references        /* Thread references */
 3074 ) {
 3075     return CtdlMakeMessageLen(
 3076         author, /* author's user structure */
 3077         recipient,      /* NULL if it's not mail */
 3078         (recipient)?strlen(recipient) : 0,
 3079         recp_cc,            /* NULL if it's not mail */
 3080         (recp_cc)?strlen(recp_cc): 0,
 3081         room,           /* room where it's going */
 3082         (room)?strlen(room): 0,
 3083         type,           /* see MES_ types in header file */
 3084         format_type,        /* variformat, plain text, MIME... */
 3085         fake_name,      /* who we're masquerading as */
 3086         (fake_name)?strlen(fake_name): 0,
 3087         my_email,           /* which of my email addresses to use (empty is ok) */
 3088         (my_email)?strlen(my_email): 0,
 3089         subject,            /* Subject (optional) */
 3090         (subject)?strlen(subject): 0,
 3091         supplied_euid,      /* ...or NULL if this is irrelevant */
 3092         (supplied_euid)?strlen(supplied_euid):0,
 3093         preformatted_text,  /* ...or NULL to read text from client */
 3094         (preformatted_text)?strlen(preformatted_text) : 0,
 3095         references,     /* Thread references */
 3096         (references)?strlen(references):0);
 3097 
 3098 }
 3099 
 3100 
 3101 /*
 3102  * Build a binary message to be saved on disk.
 3103  * (NOTE: if you supply 'preformatted_text', the buffer you give it
 3104  * will become part of the message.  This means you are no longer
 3105  * responsible for managing that memory -- it will be freed along with
 3106  * the rest of the fields when CM_Free() is called.)
 3107  */
 3108 struct CtdlMessage *CtdlMakeMessageLen(
 3109     struct ctdluser *author,    /* author's user structure */
 3110     char *recipient,        /* NULL if it's not mail */
 3111     long rcplen,
 3112     char *recp_cc,          /* NULL if it's not mail */
 3113     long cclen,
 3114     char *room,         /* room where it's going */
 3115     long roomlen,
 3116     int type,           /* see MES_ types in header file */
 3117     int format_type,        /* variformat, plain text, MIME... */
 3118     char *fake_name,        /* who we're masquerading as */
 3119     long fnlen,
 3120     char *my_email,         /* which of my email addresses to use (empty is ok) */
 3121     long myelen,
 3122     char *subject,          /* Subject (optional) */
 3123     long subjlen,
 3124     char *supplied_euid,        /* ...or NULL if this is irrelevant */
 3125     long euidlen,
 3126     char *preformatted_text,    /* ...or NULL to read text from client */
 3127     long textlen,
 3128     char *references,       /* Thread references */
 3129     long reflen
 3130 ) {
 3131     long blen;
 3132     char buf[1024];
 3133     struct CtdlMessage *msg;
 3134     StrBuf *FakeAuthor;
 3135     StrBuf *FakeEncAuthor = NULL;
 3136 
 3137     msg = malloc(sizeof(struct CtdlMessage));
 3138     memset(msg, 0, sizeof(struct CtdlMessage));
 3139     msg->cm_magic = CTDLMESSAGE_MAGIC;
 3140     msg->cm_anon_type = type;
 3141     msg->cm_format_type = format_type;
 3142 
 3143     if (recipient != NULL) rcplen = striplt(recipient);
 3144     if (recp_cc != NULL) cclen = striplt(recp_cc);
 3145 
 3146     /* Path or Return-Path */
 3147     if (myelen > 0) {
 3148         CM_SetField(msg, eMessagePath, my_email, myelen);
 3149     }
 3150     else if (!IsEmptyStr(author->fullname)) {
 3151         CM_SetField(msg, eMessagePath, author->fullname, -1);
 3152     }
 3153     convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
 3154 
 3155     blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
 3156     CM_SetField(msg, eTimestamp, buf, blen);
 3157 
 3158     if (fnlen > 0) {
 3159         FakeAuthor = NewStrBufPlain (fake_name, fnlen);
 3160     }
 3161     else {
 3162         FakeAuthor = NewStrBufPlain (author->fullname, -1);
 3163     }
 3164     StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
 3165     CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
 3166     FreeStrBuf(&FakeAuthor);
 3167 
 3168     if (!!IsEmptyStr(CC->room.QRname)) {
 3169         if (CC->room.QRflags & QR_MAILBOX) {        /* room */
 3170             CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
 3171         }
 3172         else {
 3173             CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
 3174         }
 3175     }
 3176 
 3177     if (rcplen > 0) {
 3178         CM_SetField(msg, eRecipient, recipient, rcplen);
 3179     }
 3180     if (cclen > 0) {
 3181         CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
 3182     }
 3183 
 3184     if (myelen > 0) {
 3185         CM_SetField(msg, erFc822Addr, my_email, myelen);
 3186     }
 3187     else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
 3188         CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
 3189     }
 3190 
 3191     if (subject != NULL) {
 3192         long length;
 3193         length = striplt(subject);
 3194         if (length > 0) {
 3195             long i;
 3196             long IsAscii;
 3197             IsAscii = -1;
 3198             i = 0;
 3199             while ((subject[i] != '\0') &&
 3200                    (IsAscii = isascii(subject[i]) != 0 ))
 3201                 i++;
 3202             if (IsAscii != 0)
 3203                 CM_SetField(msg, eMsgSubject, subject, subjlen);
 3204             else /* ok, we've got utf8 in the string. */
 3205             {
 3206                 char *rfc2047Subj;
 3207                 rfc2047Subj = rfc2047encode(subject, length);
 3208                 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
 3209             }
 3210 
 3211         }
 3212     }
 3213 
 3214     if (euidlen > 0) {
 3215         CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
 3216     }
 3217 
 3218     if (reflen > 0) {
 3219         CM_SetField(msg, eWeferences, references, reflen);
 3220     }
 3221 
 3222     if (preformatted_text != NULL) {
 3223         CM_SetField(msg, eMesageText, preformatted_text, textlen);
 3224     }
 3225     else {
 3226         StrBuf *MsgBody;
 3227         MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
 3228         if (MsgBody != NULL) {
 3229             CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
 3230         }
 3231     }
 3232 
 3233     return(msg);
 3234 }
 3235 
 3236 
 3237 /*
 3238  * API function to delete messages which match a set of criteria
 3239  * (returns the actual number of messages deleted)
 3240  */
 3241 int CtdlDeleteMessages(const char *room_name,   // which room
 3242                long *dmsgnums,      // array of msg numbers to be deleted
 3243                int num_dmsgnums,    // number of msgs to be deleted, or 0 for "any"
 3244                char *content_type   // or "" for any.  regular expressions expected.
 3245 ) {
 3246     struct ctdlroom qrbuf;
 3247     struct cdbdata *cdbfr;
 3248     long *msglist = NULL;
 3249     long *dellist = NULL;
 3250     int num_msgs = 0;
 3251     int i, j;
 3252     int num_deleted = 0;
 3253     int delete_this;
 3254     struct MetaData smi;
 3255     regex_t re;
 3256     regmatch_t pm;
 3257     int need_to_free_re = 0;
 3258 
 3259     if (content_type) if (!IsEmptyStr(content_type)) {
 3260             regcomp(&re, content_type, 0);
 3261             need_to_free_re = 1;
 3262         }
 3263     syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
 3264 
 3265     /* get room record, obtaining a lock... */
 3266     if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
 3267         syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
 3268         if (need_to_free_re) regfree(&re);
 3269         return(0);  /* room not found */
 3270     }
 3271     cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
 3272 
 3273     if (cdbfr != NULL) {
 3274         dellist = malloc(cdbfr->len);
 3275         msglist = (long *) cdbfr->ptr;
 3276         cdbfr->ptr = NULL;  /* CtdlDeleteMessages() now owns this memory */
 3277         num_msgs = cdbfr->len / sizeof(long);
 3278         cdb_free(cdbfr);
 3279     }
 3280     if (num_msgs > 0) {
 3281         int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
 3282         int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
 3283         int have_more_del = 1;
 3284 
 3285         num_msgs = sort_msglist(msglist, num_msgs);
 3286         if (num_dmsgnums > 1)
 3287             num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
 3288 /*
 3289         {
 3290             StrBuf *dbg = NewStrBuf();
 3291             for (i = 0; i < num_dmsgnums; i++)
 3292                 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
 3293             syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
 3294             FreeStrBuf(&dbg);
 3295         }
 3296 */
 3297         i = 0; j = 0;
 3298         while ((i < num_msgs) && (have_more_del)) {
 3299             delete_this = 0x00;
 3300 
 3301             /* Set/clear a bit for each criterion */
 3302 
 3303             /* 0 messages in the list or a null list means that we are
 3304              * interested in deleting any messages which meet the other criteria.
 3305              */
 3306             if (have_delmsgs) {
 3307                 delete_this |= 0x01;
 3308             }
 3309             else {
 3310                 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
 3311 
 3312                 if (i >= num_msgs)
 3313                     continue;
 3314 
 3315                 if (msglist[i] == dmsgnums[j]) {
 3316                     delete_this |= 0x01;
 3317                 }
 3318                 j++;
 3319                 have_more_del = (j < num_dmsgnums);
 3320             }
 3321 
 3322             if (have_contenttype) {
 3323                 GetMetaData(&smi, msglist[i]);
 3324                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
 3325                     delete_this |= 0x02;
 3326                 }
 3327             } else {
 3328                 delete_this |= 0x02;
 3329             }
 3330 
 3331             /* Delete message only if all bits are set */
 3332             if (delete_this == 0x03) {
 3333                 dellist[num_deleted++] = msglist[i];
 3334                 msglist[i] = 0L;
 3335             }
 3336             i++;
 3337         }
 3338 /*
 3339         {
 3340             StrBuf *dbg = NewStrBuf();
 3341             for (i = 0; i < num_deleted; i++)
 3342                 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
 3343             syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
 3344             FreeStrBuf(&dbg);
 3345         }
 3346 */
 3347         num_msgs = sort_msglist(msglist, num_msgs);
 3348         cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
 3349               msglist, (int)(num_msgs * sizeof(long)));
 3350 
 3351         if (num_msgs > 0)
 3352             qrbuf.QRhighest = msglist[num_msgs - 1];
 3353         else
 3354             qrbuf.QRhighest = 0;
 3355     }
 3356     CtdlPutRoomLock(&qrbuf);
 3357 
 3358     /* Go through the messages we pulled out of the index, and decrement
 3359      * their reference counts by 1.  If this is the only room the message
 3360      * was in, the reference count will reach zero and the message will
 3361      * automatically be deleted from the database.  We do this in a
 3362      * separate pass because there might be plug-in hooks getting called,
 3363      * and we don't want that happening during an S_ROOMS critical
 3364      * section.
 3365      */
 3366     if (num_deleted) {
 3367         for (i=0; i<num_deleted; ++i) {
 3368             PerformDeleteHooks(qrbuf.QRname, dellist[i]);
 3369         }
 3370         AdjRefCountList(dellist, num_deleted, -1);
 3371     }
 3372     /* Now free the memory we used, and go away. */
 3373     if (msglist != NULL) free(msglist);
 3374     if (dellist != NULL) free(dellist);
 3375     syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
 3376     if (need_to_free_re) regfree(&re);
 3377     return (num_deleted);
 3378 }
 3379 
 3380 
 3381 /*
 3382  * GetMetaData()  -  Get the supplementary record for a message
 3383  */
 3384 void GetMetaData(struct MetaData *smibuf, long msgnum)
 3385 {
 3386     struct cdbdata *cdbsmi;
 3387     long TheIndex;
 3388 
 3389     memset(smibuf, 0, sizeof(struct MetaData));
 3390     smibuf->meta_msgnum = msgnum;
 3391     smibuf->meta_refcount = 1;  /* Default reference count is 1 */
 3392 
 3393     /* Use the negative of the message number for its supp record index */
 3394     TheIndex = (0L - msgnum);
 3395 
 3396     cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
 3397     if (cdbsmi == NULL) {
 3398         return;         /* record not found; leave it alone */
 3399     }
 3400     memcpy(smibuf, cdbsmi->ptr,
 3401            ((cdbsmi->len > sizeof(struct MetaData)) ?
 3402         sizeof(struct MetaData) : cdbsmi->len)
 3403     );
 3404     cdb_free(cdbsmi);
 3405     return;
 3406 }
 3407 
 3408 
 3409 /*
 3410  * PutMetaData()  -  (re)write supplementary record for a message
 3411  */
 3412 void PutMetaData(struct MetaData *smibuf)
 3413 {
 3414     long TheIndex;
 3415 
 3416     /* Use the negative of the message number for the metadata db index */
 3417     TheIndex = (0L - smibuf->meta_msgnum);
 3418 
 3419     cdb_store(CDB_MSGMAIN,
 3420           &TheIndex, (int)sizeof(long),
 3421           smibuf, (int)sizeof(struct MetaData)
 3422     );
 3423 }
 3424 
 3425 
 3426 /*
 3427  * Convenience function to process a big block of AdjRefCount() operations
 3428  */
 3429 void AdjRefCountList(long *msgnum, long nmsg, int incr)
 3430 {
 3431     long i;
 3432 
 3433     for (i = 0; i < nmsg; i++) {
 3434         AdjRefCount(msgnum[i], incr);
 3435     }
 3436 }
 3437 
 3438 
 3439 /*
 3440  * AdjRefCount - adjust the reference count for a message.  We need to delete from disk any message whose reference count reaches zero.
 3441  */
 3442 void AdjRefCount(long msgnum, int incr)
 3443 {
 3444     struct MetaData smi;
 3445     long delnum;
 3446 
 3447     /* This is a *tight* critical section; please keep it that way, as
 3448      * it may get called while nested in other critical sections.  
 3449      * Complicating this any further will surely cause deadlock!
 3450      */
 3451     begin_critical_section(S_SUPPMSGMAIN);
 3452     GetMetaData(&smi, msgnum);
 3453     smi.meta_refcount += incr;
 3454     PutMetaData(&smi);
 3455     end_critical_section(S_SUPPMSGMAIN);
 3456     syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
 3457 
 3458     /* If the reference count is now zero, delete both the message and its metadata record.
 3459      */
 3460     if (smi.meta_refcount == 0) {
 3461         syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
 3462         
 3463         /* Call delete hooks with NULL room to show it has gone altogether */
 3464         PerformDeleteHooks(NULL, msgnum);
 3465 
 3466         /* Remove from message base */
 3467         delnum = msgnum;
 3468         cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
 3469         cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
 3470 
 3471         /* Remove metadata record */
 3472         delnum = (0L - msgnum);
 3473         cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
 3474     }
 3475 }
 3476 
 3477 
 3478 /*
 3479  * Write a generic object to this room
 3480  *
 3481  * Note: this could be much more efficient.  Right now we use two temporary
 3482  * files, and still pull the message into memory as with all others.
 3483  */
 3484 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
 3485              char *content_type,        /* MIME type of this object */
 3486              char *raw_message,         /* Data to be written */
 3487              off_t raw_length,          /* Size of raw_message */
 3488              struct ctdluser *is_mailbox,   /* Mailbox room? */
 3489              int is_binary,         /* Is encoding necessary? */
 3490              int is_unique,         /* Del others of this type? */
 3491              unsigned int flags         /* Internal save flags */
 3492 ) {
 3493     struct ctdlroom qrbuf;
 3494     char roomname[ROOMNAMELEN];
 3495     struct CtdlMessage *msg;
 3496     StrBuf *encoded_message = NULL;
 3497 
 3498     if (is_mailbox != NULL) {
 3499         CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
 3500     }
 3501     else {
 3502         safestrncpy(roomname, req_room, sizeof(roomname));
 3503     }
 3504 
 3505     syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
 3506 
 3507     if (is_binary) {
 3508         encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
 3509     }
 3510     else {
 3511         encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
 3512     }
 3513 
 3514     StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
 3515     StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
 3516     StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
 3517 
 3518     if (is_binary) {
 3519         StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
 3520     }
 3521     else {
 3522         StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
 3523     }
 3524 
 3525     if (is_binary) {
 3526         StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
 3527     }
 3528     else {
 3529         StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
 3530     }
 3531 
 3532     syslog(LOG_DEBUG, "msgbase: allocating");
 3533     msg = malloc(sizeof(struct CtdlMessage));
 3534     memset(msg, 0, sizeof(struct CtdlMessage));
 3535     msg->cm_magic = CTDLMESSAGE_MAGIC;
 3536     msg->cm_anon_type = MES_NORMAL;
 3537     msg->cm_format_type = 4;
 3538     CM_SetField(msg, eAuthor, CC->user.fullname, -1);
 3539     CM_SetField(msg, eOriginalRoom, req_room, -1);
 3540     msg->cm_flags = flags;
 3541     
 3542     CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
 3543 
 3544     /* Create the requested room if we have to. */
 3545     if (CtdlGetRoom(&qrbuf, roomname) != 0) {
 3546         CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
 3547     }
 3548     /* If the caller specified this object as unique, delete all
 3549      * other objects of this type that are currently in the room.
 3550      */
 3551     if (is_unique) {
 3552         syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
 3553                CtdlDeleteMessages(roomname, NULL, 0, content_type)
 3554             );
 3555     }
 3556     /* Now write the data */
 3557     CtdlSubmitMsg(msg, NULL, roomname);
 3558     CM_Free(msg);
 3559 }
 3560 
 3561 
 3562 /************************************************************************/
 3563 /*                      MODULE INITIALIZATION                           */
 3564 /************************************************************************/
 3565 
 3566 CTDL_MODULE_INIT(msgbase)
 3567 {
 3568     if (!threading) {
 3569         FillMsgKeyLookupTable();
 3570     }
 3571 
 3572         /* return our module id for the log */
 3573     return "msgbase";
 3574 }