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