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