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