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