move config message loading from msgbase.c -> config.c
[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  * Back end for the MSGS command: output message number only.
356  */
357 void simple_listing(long msgnum, void *userdata)
358 {
359         cprintf("%ld\n", msgnum);
360 }
361
362
363
364 /*
365  * Back end for the MSGS command: output header summary.
366  */
367 void headers_listing(long msgnum, void *userdata)
368 {
369         struct CtdlMessage *msg;
370
371         msg = CtdlFetchMessage(msgnum, 0);
372         if (msg == NULL) {
373                 cprintf("%ld|0|||||\n", msgnum);
374                 return;
375         }
376
377         cprintf("%ld|%s|%s|%s|%s|%s|\n",
378                 msgnum,
379                 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
380                 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
381                 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
382                 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
383                 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
384         );
385         CM_Free(msg);
386 }
387
388 /*
389  * Back end for the MSGS command: output EUID header.
390  */
391 void headers_euid(long msgnum, void *userdata)
392 {
393         struct CtdlMessage *msg;
394
395         msg = CtdlFetchMessage(msgnum, 0);
396         if (msg == NULL) {
397                 cprintf("%ld||\n", msgnum);
398                 return;
399         }
400
401         cprintf("%ld|%s|%s\n", 
402                 msgnum, 
403                 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
404                 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
405         CM_Free(msg);
406 }
407
408
409
410
411
412 /* Determine if a given message matches the fields in a message template.
413  * Return 0 for a successful match.
414  */
415 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
416         int i;
417
418         /* If there aren't any fields in the template, all messages will
419          * match.
420          */
421         if (template == NULL) return(0);
422
423         /* Null messages are bogus. */
424         if (msg == NULL) return(1);
425
426         for (i='A'; i<='Z'; ++i) {
427                 if (template->cm_fields[i] != NULL) {
428                         if (msg->cm_fields[i] == NULL) {
429                                 /* Considered equal if temmplate is empty string */
430                                 if (IsEmptyStr(template->cm_fields[i])) continue;
431                                 return 1;
432                         }
433                         if (strcasecmp(msg->cm_fields[i],
434                                 template->cm_fields[i])) return 1;
435                 }
436         }
437
438         /* All compares succeeded: we have a match! */
439         return 0;
440 }
441
442
443
444 /*
445  * Retrieve the "seen" message list for the current room.
446  */
447 void CtdlGetSeen(char *buf, int which_set) {
448         struct CitContext *CCC = CC;
449         visit vbuf;
450
451         /* Learn about the user and room in question */
452         CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
453
454         if (which_set == ctdlsetseen_seen)
455                 safestrncpy(buf, vbuf.v_seen, SIZ);
456         if (which_set == ctdlsetseen_answered)
457                 safestrncpy(buf, vbuf.v_answered, SIZ);
458 }
459
460
461
462 /*
463  * Manipulate the "seen msgs" string (or other message set strings)
464  */
465 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
466                 int target_setting, int which_set,
467                 struct ctdluser *which_user, struct ctdlroom *which_room) {
468         struct CitContext *CCC = CC;
469         struct cdbdata *cdbfr;
470         int i, k;
471         int is_seen = 0;
472         int was_seen = 0;
473         long lo = (-1L);
474         long hi = (-1L); /// TODO: we just write here. y?
475         visit vbuf;
476         long *msglist;
477         int num_msgs = 0;
478         StrBuf *vset;
479         StrBuf *setstr;
480         StrBuf *lostr;
481         StrBuf *histr;
482         const char *pvset;
483         char *is_set;   /* actually an array of booleans */
484
485         /* Don't bother doing *anything* if we were passed a list of zero messages */
486         if (num_target_msgnums < 1) {
487                 return;
488         }
489
490         /* If no room was specified, we go with the current room. */
491         if (!which_room) {
492                 which_room = &CCC->room;
493         }
494
495         /* If no user was specified, we go with the current user. */
496         if (!which_user) {
497                 which_user = &CCC->user;
498         }
499
500         MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
501                    num_target_msgnums, target_msgnums[0],
502                    (target_setting ? "SET" : "CLEAR"),
503                    which_set,
504                    which_room->QRname);
505
506         /* Learn about the user and room in question */
507         CtdlGetRelationship(&vbuf, which_user, which_room);
508
509         /* Load the message list */
510         cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
511         if (cdbfr != NULL) {
512                 msglist = (long *) cdbfr->ptr;
513                 cdbfr->ptr = NULL;      /* CtdlSetSeen() now owns this memory */
514                 num_msgs = cdbfr->len / sizeof(long);
515                 cdb_free(cdbfr);
516         } else {
517                 return; /* No messages at all?  No further action. */
518         }
519
520         is_set = malloc(num_msgs * sizeof(char));
521         memset(is_set, 0, (num_msgs * sizeof(char)) );
522
523         /* Decide which message set we're manipulating */
524         switch(which_set) {
525         case ctdlsetseen_seen:
526                 vset = NewStrBufPlain(vbuf.v_seen, -1);
527                 break;
528         case ctdlsetseen_answered:
529                 vset = NewStrBufPlain(vbuf.v_answered, -1);
530                 break;
531         default:
532                 vset = NewStrBuf();
533         }
534
535
536 #if 0   /* This is a special diagnostic section.  Do not allow it to run during normal operation. */
537         MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
538         for (i=0; i<num_msgs; ++i) {
539                 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
540         }
541         MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
542         for (k=0; k<num_target_msgnums; ++k) {
543                 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
544         }
545 #endif
546
547         MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
548
549         /* Translate the existing sequence set into an array of booleans */
550         setstr = NewStrBuf();
551         lostr = NewStrBuf();
552         histr = NewStrBuf();
553         pvset = NULL;
554         while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
555
556                 StrBufExtract_token(lostr, setstr, 0, ':');
557                 if (StrBufNum_tokens(setstr, ':') >= 2) {
558                         StrBufExtract_token(histr, setstr, 1, ':');
559                 }
560                 else {
561                         FlushStrBuf(histr);
562                         StrBufAppendBuf(histr, lostr, 0);
563                 }
564                 lo = StrTol(lostr);
565                 if (!strcmp(ChrPtr(histr), "*")) {
566                         hi = LONG_MAX;
567                 }
568                 else {
569                         hi = StrTol(histr);
570                 }
571
572                 for (i = 0; i < num_msgs; ++i) {
573                         if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
574                                 is_set[i] = 1;
575                         }
576                 }
577         }
578         FreeStrBuf(&setstr);
579         FreeStrBuf(&lostr);
580         FreeStrBuf(&histr);
581
582
583         /* Now translate the array of booleans back into a sequence set */
584         FlushStrBuf(vset);
585         was_seen = 0;
586         lo = (-1);
587         hi = (-1);
588
589         for (i=0; i<num_msgs; ++i) {
590                 is_seen = is_set[i];
591
592                 /* Apply changes */
593                 for (k=0; k<num_target_msgnums; ++k) {
594                         if (msglist[i] == target_msgnums[k]) {
595                                 is_seen = target_setting;
596                         }
597                 }
598
599                 if ((was_seen == 0) && (is_seen == 1)) {
600                         lo = msglist[i];
601                 }
602                 else if ((was_seen == 1) && (is_seen == 0)) {
603                         hi = msglist[i-1];
604
605                         if (StrLength(vset) > 0) {
606                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
607                         }
608                         if (lo == hi) {
609                                 StrBufAppendPrintf(vset, "%ld", hi);
610                         }
611                         else {
612                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
613                         }
614                 }
615
616                 if ((is_seen) && (i == num_msgs - 1)) {
617                         if (StrLength(vset) > 0) {
618                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
619                         }
620                         if ((i==0) || (was_seen == 0)) {
621                                 StrBufAppendPrintf(vset, "%ld", msglist[i]);
622                         }
623                         else {
624                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
625                         }
626                 }
627
628                 was_seen = is_seen;
629         }
630
631         /*
632          * We will have to stuff this string back into a 4096 byte buffer, so if it's
633          * larger than that now, truncate it by removing tokens from the beginning.
634          * The limit of 100 iterations is there to prevent an infinite loop in case
635          * something unexpected happens.
636          */
637         int number_of_truncations = 0;
638         while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
639                 StrBufRemove_token(vset, 0, ',');
640                 ++number_of_truncations;
641         }
642
643         /*
644          * If we're truncating the sequence set of messages marked with the 'seen' flag,
645          * we want the earliest messages (the truncated ones) to be marked, not unmarked.
646          * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
647          */
648         if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
649                 StrBuf *first_tok;
650                 first_tok = NewStrBuf();
651                 StrBufExtract_token(first_tok, vset, 0, ',');
652                 StrBufRemove_token(vset, 0, ',');
653
654                 if (StrBufNum_tokens(first_tok, ':') > 1) {
655                         StrBufRemove_token(first_tok, 0, ':');
656                 }
657                 
658                 StrBuf *new_set;
659                 new_set = NewStrBuf();
660                 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
661                 StrBufAppendBuf(new_set, first_tok, 0);
662                 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
663                 StrBufAppendBuf(new_set, vset, 0);
664
665                 FreeStrBuf(&vset);
666                 FreeStrBuf(&first_tok);
667                 vset = new_set;
668         }
669
670         MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
671
672         /* Decide which message set we're manipulating */
673         switch (which_set) {
674                 case ctdlsetseen_seen:
675                         safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
676                         break;
677                 case ctdlsetseen_answered:
678                         safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
679                         break;
680         }
681
682         free(is_set);
683         free(msglist);
684         CtdlSetRelationship(&vbuf, which_user, which_room);
685         FreeStrBuf(&vset);
686 }
687
688
689 /*
690  * API function to perform an operation for each qualifying message in the
691  * current room.  (Returns the number of messages processed.)
692  */
693 int CtdlForEachMessage(int mode, long ref, char *search_string,
694                         char *content_type,
695                         struct CtdlMessage *compare,
696                         ForEachMsgCallback CallBack,
697                         void *userdata)
698 {
699         struct CitContext *CCC = CC;
700         int a, i, j;
701         visit vbuf;
702         struct cdbdata *cdbfr;
703         long *msglist = NULL;
704         int num_msgs = 0;
705         int num_processed = 0;
706         long thismsg;
707         struct MetaData smi;
708         struct CtdlMessage *msg = NULL;
709         int is_seen = 0;
710         long lastold = 0L;
711         int printed_lastold = 0;
712         int num_search_msgs = 0;
713         long *search_msgs = NULL;
714         regex_t re;
715         int need_to_free_re = 0;
716         regmatch_t pm;
717
718         if ((content_type) && (!IsEmptyStr(content_type))) {
719                 regcomp(&re, content_type, 0);
720                 need_to_free_re = 1;
721         }
722
723         /* Learn about the user and room in question */
724         if (server_shutting_down) {
725                 if (need_to_free_re) regfree(&re);
726                 return -1;
727         }
728         CtdlGetUser(&CCC->user, CCC->curr_user);
729
730         if (server_shutting_down) {
731                 if (need_to_free_re) regfree(&re);
732                 return -1;
733         }
734         CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
735
736         if (server_shutting_down) {
737                 if (need_to_free_re) regfree(&re);
738                 return -1;
739         }
740
741         /* Load the message list */
742         cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
743         if (cdbfr == NULL) {
744                 if (need_to_free_re) regfree(&re);
745                 return 0;       /* No messages at all?  No further action. */
746         }
747
748         msglist = (long *) cdbfr->ptr;
749         num_msgs = cdbfr->len / sizeof(long);
750
751         cdbfr->ptr = NULL;      /* clear this so that cdb_free() doesn't free it */
752         cdb_free(cdbfr);        /* we own this memory now */
753
754         /*
755          * Now begin the traversal.
756          */
757         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
758
759                 /* If the caller is looking for a specific MIME type, filter
760                  * out all messages which are not of the type requested.
761                  */
762                 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
763
764                         /* This call to GetMetaData() sits inside this loop
765                          * so that we only do the extra database read per msg
766                          * if we need to.  Doing the extra read all the time
767                          * really kills the server.  If we ever need to use
768                          * metadata for another search criterion, we need to
769                          * move the read somewhere else -- but still be smart
770                          * enough to only do the read if the caller has
771                          * specified something that will need it.
772                          */
773                         if (server_shutting_down) {
774                                 if (need_to_free_re) regfree(&re);
775                                 free(msglist);
776                                 return -1;
777                         }
778                         GetMetaData(&smi, msglist[a]);
779
780                         /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
781                         if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
782                                 msglist[a] = 0L;
783                         }
784                 }
785         }
786
787         num_msgs = sort_msglist(msglist, num_msgs);
788
789         /* If a template was supplied, filter out the messages which
790          * don't match.  (This could induce some delays!)
791          */
792         if (num_msgs > 0) {
793                 if (compare != NULL) {
794                         for (a = 0; a < num_msgs; ++a) {
795                                 if (server_shutting_down) {
796                                         if (need_to_free_re) regfree(&re);
797                                         free(msglist);
798                                         return -1;
799                                 }
800                                 msg = CtdlFetchMessage(msglist[a], 1);
801                                 if (msg != NULL) {
802                                         if (CtdlMsgCmp(msg, compare)) {
803                                                 msglist[a] = 0L;
804                                         }
805                                         CM_Free(msg);
806                                 }
807                         }
808                 }
809         }
810
811         /* If a search string was specified, get a message list from
812          * the full text index and remove messages which aren't on both
813          * lists.
814          *
815          * How this works:
816          * Since the lists are sorted and strictly ascending, and the
817          * output list is guaranteed to be shorter than or equal to the
818          * input list, we overwrite the bottom of the input list.  This
819          * eliminates the need to memmove big chunks of the list over and
820          * over again.
821          */
822         if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
823
824                 /* Call search module via hook mechanism.
825                  * NULL means use any search function available.
826                  * otherwise replace with a char * to name of search routine
827                  */
828                 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
829
830                 if (num_search_msgs > 0) {
831         
832                         int orig_num_msgs;
833
834                         orig_num_msgs = num_msgs;
835                         num_msgs = 0;
836                         for (i=0; i<orig_num_msgs; ++i) {
837                                 for (j=0; j<num_search_msgs; ++j) {
838                                         if (msglist[i] == search_msgs[j]) {
839                                                 msglist[num_msgs++] = msglist[i];
840                                         }
841                                 }
842                         }
843                 }
844                 else {
845                         num_msgs = 0;   /* No messages qualify */
846                 }
847                 if (search_msgs != NULL) free(search_msgs);
848
849                 /* Now that we've purged messages which don't contain the search
850                  * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
851                  * point on.
852                  */
853                 mode = MSGS_ALL;
854         }
855
856         /*
857          * Now iterate through the message list, according to the
858          * criteria supplied by the caller.
859          */
860         if (num_msgs > 0)
861                 for (a = 0; a < num_msgs; ++a) {
862                         if (server_shutting_down) {
863                                 if (need_to_free_re) regfree(&re);
864                                 free(msglist);
865                                 return num_processed;
866                         }
867                         thismsg = msglist[a];
868                         if (mode == MSGS_ALL) {
869                                 is_seen = 0;
870                         }
871                         else {
872                                 is_seen = is_msg_in_sequence_set(
873                                                         vbuf.v_seen, thismsg);
874                                 if (is_seen) lastold = thismsg;
875                         }
876                         if ((thismsg > 0L)
877                             && (
878
879                                        (mode == MSGS_ALL)
880                                        || ((mode == MSGS_OLD) && (is_seen))
881                                        || ((mode == MSGS_NEW) && (!is_seen))
882                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
883                                    || ((mode == MSGS_FIRST) && (a < ref))
884                                 || ((mode == MSGS_GT) && (thismsg > ref))
885                                 || ((mode == MSGS_LT) && (thismsg < ref))
886                                 || ((mode == MSGS_EQ) && (thismsg == ref))
887                             )
888                             ) {
889                                 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
890                                         if (CallBack)
891                                                 CallBack(lastold, userdata);
892                                         printed_lastold = 1;
893                                         ++num_processed;
894                                 }
895                                 if (CallBack) CallBack(thismsg, userdata);
896                                 ++num_processed;
897                         }
898                 }
899         if (need_to_free_re) regfree(&re);
900
901         /*
902          * We cache the most recent msglist in order to do security checks later
903          */
904         if (CCC->client_socket > 0) {
905                 if (CCC->cached_msglist != NULL) {
906                         free(CCC->cached_msglist);
907                 }
908                 CCC->cached_msglist = msglist;
909                 CCC->cached_num_msgs = num_msgs;
910         }
911         else {
912                 free(msglist);
913         }
914
915         return num_processed;
916 }
917
918
919
920 /*
921  * cmd_msgs()  -  get list of message #'s in this room
922  *              implements the MSGS server command using CtdlForEachMessage()
923  */
924 void cmd_msgs(char *cmdbuf)
925 {
926         int mode = 0;
927         char which[16];
928         char buf[256];
929         char tfield[256];
930         char tvalue[256];
931         int cm_ref = 0;
932         int i;
933         int with_template = 0;
934         struct CtdlMessage *template = NULL;
935         char search_string[1024];
936         ForEachMsgCallback CallBack;
937
938         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
939
940         extract_token(which, cmdbuf, 0, '|', sizeof which);
941         cm_ref = extract_int(cmdbuf, 1);
942         extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
943         with_template = extract_int(cmdbuf, 2);
944         switch (extract_int(cmdbuf, 3))
945         {
946         default:
947         case MSG_HDRS_BRIEF:
948                 CallBack = simple_listing;
949                 break;
950         case MSG_HDRS_ALL:
951                 CallBack = headers_listing;
952                 break;
953         case MSG_HDRS_EUID:
954                 CallBack = headers_euid;
955                 break;
956         }
957
958         strcat(which, "   ");
959         if (!strncasecmp(which, "OLD", 3))
960                 mode = MSGS_OLD;
961         else if (!strncasecmp(which, "NEW", 3))
962                 mode = MSGS_NEW;
963         else if (!strncasecmp(which, "FIRST", 5))
964                 mode = MSGS_FIRST;
965         else if (!strncasecmp(which, "LAST", 4))
966                 mode = MSGS_LAST;
967         else if (!strncasecmp(which, "GT", 2))
968                 mode = MSGS_GT;
969         else if (!strncasecmp(which, "LT", 2))
970                 mode = MSGS_LT;
971         else if (!strncasecmp(which, "SEARCH", 6))
972                 mode = MSGS_SEARCH;
973         else
974                 mode = MSGS_ALL;
975
976         if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
977                 cprintf("%d Full text index is not enabled on this server.\n",
978                         ERROR + CMD_NOT_SUPPORTED);
979                 return;
980         }
981
982         if (with_template) {
983                 unbuffer_output();
984                 cprintf("%d Send template then receive message list\n",
985                         START_CHAT_MODE);
986                 template = (struct CtdlMessage *)
987                         malloc(sizeof(struct CtdlMessage));
988                 memset(template, 0, sizeof(struct CtdlMessage));
989                 template->cm_magic = CTDLMESSAGE_MAGIC;
990                 template->cm_anon_type = MES_NORMAL;
991
992                 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
993                         long tValueLen;
994                         extract_token(tfield, buf, 0, '|', sizeof tfield);
995                         tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
996                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
997                                 if (!strcasecmp(tfield, msgkeys[i])) {
998                                         CM_SetField(template, i, tvalue, tValueLen);
999                                 }
1000                         }
1001                 }
1002                 buffer_output();
1003         }
1004         else {
1005                 cprintf("%d  \n", LISTING_FOLLOWS);
1006         }
1007
1008         CtdlForEachMessage(mode,
1009                            ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
1010                            ( (mode == MSGS_SEARCH) ? search_string : NULL ),
1011                            NULL,
1012                            template,
1013                            CallBack,
1014                            NULL);
1015         if (template != NULL) CM_Free(template);
1016         cprintf("000\n");
1017 }
1018
1019
1020 /*
1021  * memfmout()  -  Citadel text formatter and paginator.
1022  *           Although the original purpose of this routine was to format
1023  *           text to the reader's screen width, all we're really using it
1024  *           for here is to format text out to 80 columns before sending it
1025  *           to the client.  The client software may reformat it again.
1026  */
1027 void memfmout(
1028         char *mptr,             /* where are we going to get our text from? */
1029         const char *nl          /* string to terminate lines with */
1030 ) {
1031         struct CitContext *CCC = CC;
1032         int column = 0;
1033         unsigned char ch = 0;
1034         char outbuf[1024];
1035         int len = 0;
1036         int nllen = 0;
1037
1038         if (!mptr) return;
1039         nllen = strlen(nl);
1040         while (ch=*(mptr++), ch != 0) {
1041
1042                 if (ch == '\n') {
1043                         if (client_write(outbuf, len) == -1)
1044                         {
1045                                 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1046                                 return;
1047                         }
1048                         len = 0;
1049                         if (client_write(nl, nllen) == -1)
1050                         {
1051                                 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1052                                 return;
1053                         }
1054                         column = 0;
1055                 }
1056                 else if (ch == '\r') {
1057                         /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
1058                 }
1059                 else if (isspace(ch)) {
1060                         if (column > 72) {              /* Beyond 72 columns, break on the next space */
1061                                 if (client_write(outbuf, len) == -1)
1062                                 {
1063                                         MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1064                                         return;
1065                                 }
1066                                 len = 0;
1067                                 if (client_write(nl, nllen) == -1)
1068                                 {
1069                                         MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1070                                         return;
1071                                 }
1072                                 column = 0;
1073                         }
1074                         else {
1075                                 outbuf[len++] = ch;
1076                                 ++column;
1077                         }
1078                 }
1079                 else {
1080                         outbuf[len++] = ch;
1081                         ++column;
1082                         if (column > 1000) {            /* Beyond 1000 columns, break anywhere */
1083                                 if (client_write(outbuf, len) == -1)
1084                                 {
1085                                         MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1086                                         return;
1087                                 }
1088                                 len = 0;
1089                                 if (client_write(nl, nllen) == -1)
1090                                 {
1091                                         MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1092                                         return;
1093                                 }
1094                                 column = 0;
1095                         }
1096                 }
1097         }
1098         if (len) {
1099                 if (client_write(outbuf, len) == -1)
1100                 {
1101                         MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1102                         return;
1103                 }
1104                 len = 0;
1105                 client_write(nl, nllen);
1106                 column = 0;
1107         }
1108 }
1109
1110
1111
1112 /*
1113  * Callback function for mime parser that simply lists the part
1114  */
1115 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1116                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1117                     char *cbid, void *cbuserdata)
1118 {
1119         struct ma_info *ma;
1120         
1121         ma = (struct ma_info *)cbuserdata;
1122         if (ma->is_ma == 0) {
1123                 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1124                         name, 
1125                         filename, 
1126                         partnum, 
1127                         disp, 
1128                         cbtype, 
1129                         (long)length, 
1130                         cbid, 
1131                         cbcharset);
1132         }
1133 }
1134
1135 /* 
1136  * Callback function for multipart prefix
1137  */
1138 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1139                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1140                     char *cbid, void *cbuserdata)
1141 {
1142         struct ma_info *ma;
1143         
1144         ma = (struct ma_info *)cbuserdata;
1145         if (!strcasecmp(cbtype, "multipart/alternative")) {
1146                 ++ma->is_ma;
1147         }
1148
1149         if (ma->is_ma == 0) {
1150                 cprintf("pref=%s|%s\n", partnum, cbtype);
1151         }
1152 }
1153
1154 /* 
1155  * Callback function for multipart sufffix
1156  */
1157 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1158                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1159                     char *cbid, void *cbuserdata)
1160 {
1161         struct ma_info *ma;
1162         
1163         ma = (struct ma_info *)cbuserdata;
1164         if (ma->is_ma == 0) {
1165                 cprintf("suff=%s|%s\n", partnum, cbtype);
1166         }
1167         if (!strcasecmp(cbtype, "multipart/alternative")) {
1168                 --ma->is_ma;
1169         }
1170 }
1171
1172
1173 /*
1174  * Callback function for mime parser that opens a section for downloading
1175  */
1176 void mime_download(char *name, char *filename, char *partnum, char *disp,
1177                    void *content, char *cbtype, char *cbcharset, size_t length,
1178                    char *encoding, char *cbid, void *cbuserdata)
1179 {
1180         int rv = 0;
1181         CitContext *CCC = MyContext();
1182
1183         /* Silently go away if there's already a download open. */
1184         if (CCC->download_fp != NULL)
1185                 return;
1186
1187         if (
1188                 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1189         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1190         ) {
1191                 CCC->download_fp = tmpfile();
1192                 if (CCC->download_fp == NULL) {
1193                         MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1194                                     strerror(errno));
1195                         cprintf("%d cannot open temporary file: %s\n",
1196                                 ERROR + INTERNAL_ERROR, strerror(errno));
1197                         return;
1198                 }
1199         
1200                 rv = fwrite(content, length, 1, CCC->download_fp);
1201                 if (rv <= 0) {
1202                         MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1203                                    strerror(errno));
1204                         cprintf("%d unable to write tempfile.\n",
1205                                 ERROR + TOO_BIG);
1206                         fclose(CCC->download_fp);
1207                         CCC->download_fp = NULL;
1208                         return;
1209                 }
1210                 fflush(CCC->download_fp);
1211                 rewind(CCC->download_fp);
1212         
1213                 OpenCmdResult(filename, cbtype);
1214         }
1215 }
1216
1217
1218
1219 /*
1220  * Callback function for mime parser that outputs a section all at once.
1221  * We can specify the desired section by part number *or* content-id.
1222  */
1223 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1224                    void *content, char *cbtype, char *cbcharset, size_t length,
1225                    char *encoding, char *cbid, void *cbuserdata)
1226 {
1227         int *found_it = (int *)cbuserdata;
1228
1229         if (
1230                 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1231         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1232         ) {
1233                 *found_it = 1;
1234                 cprintf("%d %d|-1|%s|%s|%s\n",
1235                         BINARY_FOLLOWS,
1236                         (int)length,
1237                         filename,
1238                         cbtype,
1239                         cbcharset
1240                 );
1241                 client_write(content, length);
1242         }
1243 }
1244
1245
1246 /*
1247  * Load a message from disk into memory.
1248  * This is used by CtdlOutputMsg() and other fetch functions.
1249  *
1250  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1251  *       using the CtdlMessageFree() function.
1252  */
1253 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1254 {
1255         struct CitContext *CCC = CC;
1256         struct cdbdata *dmsgtext;
1257         struct CtdlMessage *ret = NULL;
1258         char *mptr;
1259         char *upper_bound;
1260         cit_uint8_t ch;
1261         cit_uint8_t field_header;
1262
1263         MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1264         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1265         if (dmsgtext == NULL) {
1266                 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1267                 return NULL;
1268         }
1269         mptr = dmsgtext->ptr;
1270         upper_bound = mptr + dmsgtext->len;
1271
1272         /* Parse the three bytes that begin EVERY message on disk.
1273          * The first is always 0xFF, the on-disk magic number.
1274          * The second is the anonymous/public type byte.
1275          * The third is the format type byte (vari, fixed, or MIME).
1276          */
1277         ch = *mptr++;
1278         if (ch != 255) {
1279                 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1280                 cdb_free(dmsgtext);
1281                 return NULL;
1282         }
1283         ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1284         memset(ret, 0, sizeof(struct CtdlMessage));
1285
1286         ret->cm_magic = CTDLMESSAGE_MAGIC;
1287         ret->cm_anon_type = *mptr++;    /* Anon type byte */
1288         ret->cm_format_type = *mptr++;  /* Format type byte */
1289
1290         /*
1291          * The rest is zero or more arbitrary fields.  Load them in.
1292          * We're done when we encounter either a zero-length field or
1293          * have just processed the 'M' (message text) field.
1294          */
1295         do {
1296                 long len;
1297                 if (mptr >= upper_bound) {
1298                         break;
1299                 }
1300                 field_header = *mptr++;
1301                 len = strlen(mptr);
1302                 CM_SetField(ret, field_header, mptr, len);
1303
1304                 mptr += len + 1;        /* advance to next field */
1305
1306         } while ((mptr < upper_bound) && (field_header != 'M'));
1307
1308         cdb_free(dmsgtext);
1309
1310         /* Always make sure there's something in the msg text field.  If
1311          * it's NULL, the message text is most likely stored separately,
1312          * so go ahead and fetch that.  Failing that, just set a dummy
1313          * body so other code doesn't barf.
1314          */
1315         if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1316                 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1317                 if (dmsgtext != NULL) {
1318                         CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1319                         cdb_free(dmsgtext);
1320                 }
1321         }
1322         if (CM_IsEmpty(ret, eMesageText)) {
1323                 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1324         }
1325
1326         /* Perform "before read" hooks (aborting if any return nonzero) */
1327         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1328                 CM_Free(ret);
1329                 return NULL;
1330         }
1331
1332         return (ret);
1333 }
1334
1335
1336
1337 /*
1338  * Pre callback function for multipart/alternative
1339  *
1340  * NOTE: this differs from the standard behavior for a reason.  Normally when
1341  *       displaying multipart/alternative you want to show the _last_ usable
1342  *       format in the message.  Here we show the _first_ one, because it's
1343  *       usually text/plain.  Since this set of functions is designed for text
1344  *       output to non-MIME-aware clients, this is the desired behavior.
1345  *
1346  */
1347 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1348                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1349                 char *cbid, void *cbuserdata)
1350 {
1351         struct CitContext *CCC = CC;
1352         struct ma_info *ma;
1353         
1354         ma = (struct ma_info *)cbuserdata;
1355         MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);        
1356         if (!strcasecmp(cbtype, "multipart/alternative")) {
1357                 ++ma->is_ma;
1358                 ma->did_print = 0;
1359         }
1360         if (!strcasecmp(cbtype, "message/rfc822")) {
1361                 ++ma->freeze;
1362         }
1363 }
1364
1365 /*
1366  * Post callback function for multipart/alternative
1367  */
1368 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1369                 void *content, char *cbtype, char *cbcharset, size_t length,
1370                 char *encoding, char *cbid, void *cbuserdata)
1371 {
1372         struct CitContext *CCC = CC;
1373         struct ma_info *ma;
1374         
1375         ma = (struct ma_info *)cbuserdata;
1376         MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);       
1377         if (!strcasecmp(cbtype, "multipart/alternative")) {
1378                 --ma->is_ma;
1379                 ma->did_print = 0;
1380         }
1381         if (!strcasecmp(cbtype, "message/rfc822")) {
1382                 --ma->freeze;
1383         }
1384 }
1385
1386 /*
1387  * Inline callback function for mime parser that wants to display text
1388  */
1389 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1390                 void *content, char *cbtype, char *cbcharset, size_t length,
1391                 char *encoding, char *cbid, void *cbuserdata)
1392 {
1393         struct CitContext *CCC = CC;
1394         char *ptr;
1395         char *wptr;
1396         size_t wlen;
1397         struct ma_info *ma;
1398
1399         ma = (struct ma_info *)cbuserdata;
1400
1401         MSG_syslog(LOG_DEBUG,
1402                 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1403                 partnum, filename, cbtype, (long)length);
1404
1405         /*
1406          * If we're in the middle of a multipart/alternative scope and
1407          * we've already printed another section, skip this one.
1408          */     
1409         if ( (ma->is_ma) && (ma->did_print) ) {
1410                 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1411                 return;
1412         }
1413         ma->did_print = 1;
1414
1415         if ( (!strcasecmp(cbtype, "text/plain")) 
1416            || (IsEmptyStr(cbtype)) ) {
1417                 wptr = content;
1418                 if (length > 0) {
1419                         client_write(wptr, length);
1420                         if (wptr[length-1] != '\n') {
1421                                 cprintf("\n");
1422                         }
1423                 }
1424                 return;
1425         }
1426
1427         if (!strcasecmp(cbtype, "text/html")) {
1428                 ptr = html_to_ascii(content, length, 80, 0);
1429                 wlen = strlen(ptr);
1430                 client_write(ptr, wlen);
1431                 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1432                         cprintf("\n");
1433                 }
1434                 free(ptr);
1435                 return;
1436         }
1437
1438         if (ma->use_fo_hooks) {
1439                 if (PerformFixedOutputHooks(cbtype, content, length)) {
1440                 /* above function returns nonzero if it handled the part */
1441                         return;
1442                 }
1443         }
1444
1445         if (strncasecmp(cbtype, "multipart/", 10)) {
1446                 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1447                         partnum, filename, cbtype, (long)length);
1448                 return;
1449         }
1450 }
1451
1452 /*
1453  * The client is elegant and sophisticated and wants to be choosy about
1454  * MIME content types, so figure out which multipart/alternative part
1455  * we're going to send.
1456  *
1457  * We use a system of weights.  When we find a part that matches one of the
1458  * MIME types we've declared as preferential, we can store it in ma->chosen_part
1459  * and then set ma->chosen_pref to that MIME type's position in our preference
1460  * list.  If we then hit another match, we only replace the first match if
1461  * the preference value is lower.
1462  */
1463 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1464                 void *content, char *cbtype, char *cbcharset, size_t length,
1465                 char *encoding, char *cbid, void *cbuserdata)
1466 {
1467         struct CitContext *CCC = CC;
1468         char buf[1024];
1469         int i;
1470         struct ma_info *ma;
1471         
1472         ma = (struct ma_info *)cbuserdata;
1473
1474         // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1475         //       http://bugzilla.citadel.org/show_bug.cgi?id=220
1476         // I don't know if there are any side effects!  Please TEST TEST TEST
1477         //if (ma->is_ma > 0) {
1478
1479         for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1480                 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1481                 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1482                         if (i < ma->chosen_pref) {
1483                                 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1484                                 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1485                                 ma->chosen_pref = i;
1486                         }
1487                 }
1488         }
1489 }
1490
1491 /*
1492  * Now that we've chosen our preferred part, output it.
1493  */
1494 void output_preferred(char *name, 
1495                       char *filename, 
1496                       char *partnum, 
1497                       char *disp,
1498                       void *content, 
1499                       char *cbtype, 
1500                       char *cbcharset, 
1501                       size_t length,
1502                       char *encoding, 
1503                       char *cbid, 
1504                       void *cbuserdata)
1505 {
1506         struct CitContext *CCC = CC;
1507         int i;
1508         char buf[128];
1509         int add_newline = 0;
1510         char *text_content;
1511         struct ma_info *ma;
1512         char *decoded = NULL;
1513         size_t bytes_decoded;
1514         int rc = 0;
1515
1516         ma = (struct ma_info *)cbuserdata;
1517
1518         /* This is not the MIME part you're looking for... */
1519         if (strcasecmp(partnum, ma->chosen_part)) return;
1520
1521         /* If the content-type of this part is in our preferred formats
1522          * list, we can simply output it verbatim.
1523          */
1524         for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1525                 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1526                 if (!strcasecmp(buf, cbtype)) {
1527                         /* Yeah!  Go!  W00t!! */
1528                         if (ma->dont_decode == 0) 
1529                                 rc = mime_decode_now (content, 
1530                                                       length,
1531                                                       encoding,
1532                                                       &decoded,
1533                                                       &bytes_decoded);
1534                         if (rc < 0)
1535                                 break; /* Give us the chance, maybe theres another one. */
1536
1537                         if (rc == 0) text_content = (char *)content;
1538                         else {
1539                                 text_content = decoded;
1540                                 length = bytes_decoded;
1541                         }
1542
1543                         if (text_content[length-1] != '\n') {
1544                                 ++add_newline;
1545                         }
1546                         cprintf("Content-type: %s", cbtype);
1547                         if (!IsEmptyStr(cbcharset)) {
1548                                 cprintf("; charset=%s", cbcharset);
1549                         }
1550                         cprintf("\nContent-length: %d\n",
1551                                 (int)(length + add_newline) );
1552                         if (!IsEmptyStr(encoding)) {
1553                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1554                         }
1555                         else {
1556                                 cprintf("Content-transfer-encoding: 7bit\n");
1557                         }
1558                         cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1559                         cprintf("\n");
1560                         if (client_write(text_content, length) == -1)
1561                         {
1562                                 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1563                                 return;
1564                         }
1565                         if (add_newline) cprintf("\n");
1566                         if (decoded != NULL) free(decoded);
1567                         return;
1568                 }
1569         }
1570
1571         /* No translations required or possible: output as text/plain */
1572         cprintf("Content-type: text/plain\n\n");
1573         rc = 0;
1574         if (ma->dont_decode == 0)
1575                 rc = mime_decode_now (content, 
1576                                       length,
1577                                       encoding,
1578                                       &decoded,
1579                                       &bytes_decoded);
1580         if (rc < 0)
1581                 return; /* Give us the chance, maybe theres another one. */
1582         
1583         if (rc == 0) text_content = (char *)content;
1584         else {
1585                 text_content = decoded;
1586                 length = bytes_decoded;
1587         }
1588
1589         fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1590                         length, encoding, cbid, cbuserdata);
1591         if (decoded != NULL) free(decoded);
1592 }
1593
1594
1595 struct encapmsg {
1596         char desired_section[64];
1597         char *msg;
1598         size_t msglen;
1599 };
1600
1601
1602 /*
1603  * Callback function for
1604  */
1605 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1606                    void *content, char *cbtype, char *cbcharset, size_t length,
1607                    char *encoding, char *cbid, void *cbuserdata)
1608 {
1609         struct encapmsg *encap;
1610
1611         encap = (struct encapmsg *)cbuserdata;
1612
1613         /* Only proceed if this is the desired section... */
1614         if (!strcasecmp(encap->desired_section, partnum)) {
1615                 encap->msglen = length;
1616                 encap->msg = malloc(length + 2);
1617                 memcpy(encap->msg, content, length);
1618                 return;
1619         }
1620 }
1621
1622
1623 /*
1624  * Determine whether the specified message exists in the cached_msglist
1625  * (This is a security check)
1626  */
1627 int check_cached_msglist(long msgnum) {
1628         struct CitContext *CCC = CC;
1629
1630         /* cases in which we skip the check */
1631         if (!CCC) return om_ok;                                         /* not a session */
1632         if (CCC->client_socket <= 0) return om_ok;                      /* not a client session */
1633         if (CCC->cached_msglist == NULL) return om_access_denied;       /* no msglist fetched */
1634         if (CCC->cached_num_msgs == 0) return om_access_denied;         /* nothing to check */
1635
1636
1637         /* Do a binary search within the cached_msglist for the requested msgnum */
1638         int min = 0;
1639         int max = (CC->cached_num_msgs - 1);
1640
1641         while (max >= min) {
1642                 int middle = min + (max-min) / 2 ;
1643                 if (msgnum == CCC->cached_msglist[middle]) {
1644                         return om_ok;
1645                 }
1646                 if (msgnum > CC->cached_msglist[middle]) {
1647                         min = middle + 1;
1648                 }
1649                 else {
1650                         max = middle - 1;
1651                 }
1652         }
1653
1654         return om_access_denied;
1655 }
1656
1657
1658
1659 /*
1660  * Get a message off disk.  (returns om_* values found in msgbase.h)
1661  * 
1662  */
1663 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1664                   int mode,             /* how would you like that message? */
1665                   int headers_only,     /* eschew the message body? */
1666                   int do_proto,         /* do Citadel protocol responses? */
1667                   int crlf,             /* Use CRLF newlines instead of LF? */
1668                   char *section,        /* NULL or a message/rfc822 section */
1669                   int flags,            /* various flags; see msgbase.h */
1670                   char **Author,
1671                   char **Address
1672 ) {
1673         struct CitContext *CCC = CC;
1674         struct CtdlMessage *TheMessage = NULL;
1675         int retcode = CIT_OK;
1676         struct encapmsg encap;
1677         int r;
1678
1679         MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", 
1680                 msg_num, mode,
1681                 (section ? section : "<>")
1682         );
1683
1684         r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1685         if (r != om_ok) {
1686                 if (do_proto) {
1687                         if (r == om_not_logged_in) {
1688                                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1689                         }
1690                         else {
1691                                 cprintf("%d An unknown error has occurred.\n", ERROR);
1692                         }
1693                 }
1694                 return(r);
1695         }
1696
1697         /*
1698          * Check to make sure the message is actually IN this room
1699          */
1700         r = check_cached_msglist(msg_num);
1701         if (r == om_access_denied) {
1702                 /* Not in the cache?  We get ONE shot to check it again. */
1703                 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1704                 r = check_cached_msglist(msg_num);
1705         }
1706         if (r != om_ok) {
1707                 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1708                            msg_num, CCC->room.QRname
1709                 );
1710                 if (do_proto) {
1711                         if (r == om_access_denied) {
1712                                 cprintf("%d message %ld was not found in this room\n",
1713                                         ERROR + HIGHER_ACCESS_REQUIRED,
1714                                         msg_num
1715                                 );
1716                         }
1717                 }
1718                 return(r);
1719         }
1720
1721         /*
1722          * Fetch the message from disk.  If we're in HEADERS_FAST mode,
1723          * request that we don't even bother loading the body into memory.
1724          */
1725         if (headers_only == HEADERS_FAST) {
1726                 TheMessage = CtdlFetchMessage(msg_num, 0);
1727         }
1728         else {
1729                 TheMessage = CtdlFetchMessage(msg_num, 1);
1730         }
1731
1732         if (TheMessage == NULL) {
1733                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1734                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1735                 return(om_no_such_msg);
1736         }
1737
1738         /* Here is the weird form of this command, to process only an
1739          * encapsulated message/rfc822 section.
1740          */
1741         if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1742                 memset(&encap, 0, sizeof encap);
1743                 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1744                 mime_parser(TheMessage->cm_fields[eMesageText],
1745                         NULL,
1746                         *extract_encapsulated_message,
1747                         NULL, NULL, (void *)&encap, 0
1748                 );
1749
1750                 if ((Author != NULL) && (*Author == NULL))
1751                 {
1752                         *Author = TheMessage->cm_fields[eAuthor];
1753                         TheMessage->cm_fields[eAuthor] = NULL;
1754                 }
1755                 if ((Address != NULL) && (*Address == NULL))
1756                 {       
1757                         *Address = TheMessage->cm_fields[erFc822Addr];
1758                         TheMessage->cm_fields[erFc822Addr] = NULL;
1759                 }
1760                 CM_Free(TheMessage);
1761                 TheMessage = NULL;
1762
1763                 if (encap.msg) {
1764                         encap.msg[encap.msglen] = 0;
1765                         TheMessage = convert_internet_message(encap.msg);
1766                         encap.msg = NULL;       /* no free() here, TheMessage owns it now */
1767
1768                         /* Now we let it fall through to the bottom of this
1769                          * function, because TheMessage now contains the
1770                          * encapsulated message instead of the top-level
1771                          * message.  Isn't that neat?
1772                          */
1773                 }
1774                 else {
1775                         if (do_proto) {
1776                                 cprintf("%d msg %ld has no part %s\n",
1777                                         ERROR + MESSAGE_NOT_FOUND,
1778                                         msg_num,
1779                                         section);
1780                         }
1781                         retcode = om_no_such_msg;
1782                 }
1783
1784         }
1785
1786         /* Ok, output the message now */
1787         if (retcode == CIT_OK)
1788                 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1789         if ((Author != NULL) && (*Author == NULL))
1790         {
1791                 *Author = TheMessage->cm_fields[eAuthor];
1792                 TheMessage->cm_fields[eAuthor] = NULL;
1793         }
1794         if ((Address != NULL) && (*Address == NULL))
1795         {       
1796                 *Address = TheMessage->cm_fields[erFc822Addr];
1797                 TheMessage->cm_fields[erFc822Addr] = NULL;
1798         }
1799
1800         CM_Free(TheMessage);
1801
1802         return(retcode);
1803 }
1804
1805
1806
1807 void OutputCtdlMsgHeaders(
1808         struct CtdlMessage *TheMessage,
1809         int do_proto)           /* do Citadel protocol responses? */
1810 {
1811         int i;
1812         int suppress_f = 0;
1813         char buf[SIZ];
1814         char display_name[256];
1815
1816         /* begin header processing loop for Citadel message format */
1817         safestrncpy(display_name, "<unknown>", sizeof display_name);
1818         if (!CM_IsEmpty(TheMessage, eAuthor)) {
1819                 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1820                 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1821                         safestrncpy(display_name, "****", sizeof display_name);
1822                 }
1823                 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1824                         safestrncpy(display_name, "anonymous", sizeof display_name);
1825                 }
1826                 else {
1827                         safestrncpy(display_name, buf, sizeof display_name);
1828                 }
1829                 if ((is_room_aide())
1830                     && ((TheMessage->cm_anon_type == MES_ANONONLY)
1831                         || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1832                         size_t tmp = strlen(display_name);
1833                         snprintf(&display_name[tmp],
1834                                  sizeof display_name - tmp,
1835                                  " [%s]", buf);
1836                 }
1837         }
1838
1839         /* Don't show Internet address for users on the
1840          * local Citadel network.
1841          */
1842         suppress_f = 0;
1843         if (!CM_IsEmpty(TheMessage, eNodeName) &&
1844             (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
1845         {
1846                 suppress_f = 1;
1847         }
1848
1849         /* Now spew the header fields in the order we like them. */
1850         for (i=0; i< NDiskFields; ++i) {
1851                 eMsgField Field;
1852                 Field = FieldOrder[i];
1853                 if (Field != eMesageText) {
1854                         if ( (!CM_IsEmpty(TheMessage, Field))
1855                              && (msgkeys[Field] != NULL) ) {
1856                                 if ((Field == eenVelopeTo) ||
1857                                     (Field == eRecipient) ||
1858                                     (Field == eCarbonCopY)) {
1859                                         sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1860                                 }
1861                                 if (Field == eAuthor) {
1862                                         if (do_proto) cprintf("%s=%s\n",
1863                                                               msgkeys[Field],
1864                                                               display_name);
1865                                 }
1866                                 else if ((Field == erFc822Addr) && (suppress_f)) {
1867                                         /* do nothing */
1868                                 }
1869                                 /* Masquerade display name if needed */
1870                                 else {
1871                                         if (do_proto) cprintf("%s=%s\n",
1872                                                               msgkeys[Field],
1873                                                               TheMessage->cm_fields[Field]
1874                                                 );
1875                                 }
1876                         }
1877                 }
1878         }
1879
1880 }
1881
1882 void OutputRFC822MsgHeaders(
1883         struct CtdlMessage *TheMessage,
1884         int flags,              /* should the bessage be exported clean */
1885         const char *nl,
1886         char *mid, long sizeof_mid,
1887         char *suser, long sizeof_suser,
1888         char *luser, long sizeof_luser,
1889         char *fuser, long sizeof_fuser,
1890         char *snode, long sizeof_snode)
1891 {
1892         char datestamp[100];
1893         int subject_found = 0;
1894         char buf[SIZ];
1895         int i, j, k;
1896         char *mptr = NULL;
1897         char *mpptr = NULL;
1898         char *hptr;
1899
1900         for (i = 0; i < 256; ++i) {
1901                 if (TheMessage->cm_fields[i]) {
1902                         mptr = mpptr = TheMessage->cm_fields[i];
1903                                 
1904                         if (i == eAuthor) {
1905                                 safestrncpy(luser, mptr, sizeof_luser);
1906                                 safestrncpy(suser, mptr, sizeof_suser);
1907                         }
1908                         else if (i == 'Y') {
1909                                 if ((flags & QP_EADDR) != 0) {
1910                                         mptr = qp_encode_email_addrs(mptr);
1911                                 }
1912                                 sanitize_truncated_recipient(mptr);
1913                                 cprintf("CC: %s%s", mptr, nl);
1914                         }
1915                         else if (i == 'P') {
1916                                 cprintf("Return-Path: %s%s", mptr, nl);
1917                         }
1918                         else if (i == eListID) {
1919                                 cprintf("List-ID: %s%s", mptr, nl);
1920                         }
1921                         else if (i == 'V') {
1922                                 if ((flags & QP_EADDR) != 0) 
1923                                         mptr = qp_encode_email_addrs(mptr);
1924                                 hptr = mptr;
1925                                 while ((*hptr != '\0') && isspace(*hptr))
1926                                         hptr ++;
1927                                 if (!IsEmptyStr(hptr))
1928                                         cprintf("Envelope-To: %s%s", hptr, nl);
1929                         }
1930                         else if (i == 'U') {
1931                                 cprintf("Subject: %s%s", mptr, nl);
1932                                 subject_found = 1;
1933                         }
1934                         else if (i == 'I')
1935                                 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1936                         else if (i == erFc822Addr)
1937                                 safestrncpy(fuser, mptr, sizeof_fuser);
1938                         /* else if (i == 'O')
1939                            cprintf("X-Citadel-Room: %s%s",
1940                            mptr, nl); */
1941                         else if (i == 'N')
1942                                 safestrncpy(snode, mptr, sizeof_snode);
1943                         else if (i == 'R')
1944                         {
1945                                 if (haschar(mptr, '@') == 0)
1946                                 {
1947                                         sanitize_truncated_recipient(mptr);
1948                                         cprintf("To: %s@%s", mptr, config.c_fqdn);
1949                                         cprintf("%s", nl);
1950                                 }
1951                                 else
1952                                 {
1953                                         if ((flags & QP_EADDR) != 0) {
1954                                                 mptr = qp_encode_email_addrs(mptr);
1955                                         }
1956                                         sanitize_truncated_recipient(mptr);
1957                                         cprintf("To: %s", mptr);
1958                                         cprintf("%s", nl);
1959                                 }
1960                         }
1961                         else if (i == 'T') {
1962                                 datestring(datestamp, sizeof datestamp,
1963                                            atol(mptr), DATESTRING_RFC822);
1964                                 cprintf("Date: %s%s", datestamp, nl);
1965                         }
1966                         else if (i == 'W') {
1967                                 cprintf("References: ");
1968                                 k = num_tokens(mptr, '|');
1969                                 for (j=0; j<k; ++j) {
1970                                         extract_token(buf, mptr, j, '|', sizeof buf);
1971                                         cprintf("<%s>", buf);
1972                                         if (j == (k-1)) {
1973                                                 cprintf("%s", nl);
1974                                         }
1975                                         else {
1976                                                 cprintf(" ");
1977                                         }
1978                                 }
1979                         }
1980                         else if (i == eReplyTo) {
1981                                 hptr = mptr;
1982                                 while ((*hptr != '\0') && isspace(*hptr))
1983                                         hptr ++;
1984                                 if (!IsEmptyStr(hptr))
1985                                         cprintf("Reply-To: %s%s", mptr, nl);
1986                         }
1987                         if (mptr != mpptr)
1988                                 free (mptr);
1989                 }
1990         }
1991         if (subject_found == 0) {
1992                 cprintf("Subject: (no subject)%s", nl);
1993         }
1994 }
1995
1996
1997 void Dump_RFC822HeadersBody(
1998         struct CtdlMessage *TheMessage,
1999         int headers_only,       /* eschew the message body? */
2000         int flags,              /* should the bessage be exported clean? */
2001
2002         const char *nl)
2003 {
2004         cit_uint8_t prev_ch;
2005         int eoh = 0;
2006         const char *StartOfText = StrBufNOTNULL;
2007         char outbuf[1024];
2008         int outlen = 0;
2009         int nllen = strlen(nl);
2010         char *mptr;
2011
2012         mptr = TheMessage->cm_fields[eMesageText];
2013
2014
2015         prev_ch = '\0';
2016         while (*mptr != '\0') {
2017                 if (*mptr == '\r') {
2018                         /* do nothing */
2019                 }
2020                 else {
2021                         if ((!eoh) &&
2022                             (*mptr == '\n'))
2023                         {
2024                                 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2025                                 if (!eoh)
2026                                         eoh = *(mptr+1) == '\n';
2027                                 if (eoh)
2028                                 {
2029                                         StartOfText = mptr;
2030                                         StartOfText = strchr(StartOfText, '\n');
2031                                         StartOfText = strchr(StartOfText, '\n');
2032                                 }
2033                         }
2034                         if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2035                             ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2036                             ((headers_only != HEADERS_NONE) && 
2037                              (headers_only != HEADERS_ONLY))
2038                                 ) {
2039                                 if (*mptr == '\n') {
2040                                         memcpy(&outbuf[outlen], nl, nllen);
2041                                         outlen += nllen;
2042                                         outbuf[outlen] = '\0';
2043                                 }
2044                                 else {
2045                                         outbuf[outlen++] = *mptr;
2046                                 }
2047                         }
2048                 }
2049                 if (flags & ESC_DOT)
2050                 {
2051                         if ((prev_ch == '\n') && 
2052                             (*mptr == '.') && 
2053                             ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2054                         {
2055                                 outbuf[outlen++] = '.';
2056                         }
2057                         prev_ch = *mptr;
2058                 }
2059                 ++mptr;
2060                 if (outlen > 1000) {
2061                         if (client_write(outbuf, outlen) == -1)
2062                         {
2063                                 struct CitContext *CCC = CC;
2064                                 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2065                                 return;
2066                         }
2067                         outlen = 0;
2068                 }
2069         }
2070         if (outlen > 0) {
2071                 client_write(outbuf, outlen);
2072         }
2073 }
2074
2075
2076
2077 /* If the format type on disk is 1 (fixed-format), then we want
2078  * everything to be output completely literally ... regardless of
2079  * what message transfer format is in use.
2080  */
2081 void DumpFormatFixed(
2082         struct CtdlMessage *TheMessage,
2083         int mode,               /* how would you like that message? */
2084         const char *nl)
2085 {
2086         cit_uint8_t ch;
2087         char buf[SIZ];
2088         int buflen;
2089         int xlline = 0;
2090         int nllen = strlen (nl);
2091         char *mptr;
2092
2093         mptr = TheMessage->cm_fields[eMesageText];
2094         
2095         if (mode == MT_MIME) {
2096                 cprintf("Content-type: text/plain\n\n");
2097         }
2098         *buf = '\0';
2099         buflen = 0;
2100         while (ch = *mptr++, ch > 0) {
2101                 if (ch == '\n')
2102                         ch = '\r';
2103
2104                 if ((buflen > 250) && (!xlline)){
2105                         int tbuflen;
2106                         tbuflen = buflen;
2107
2108                         while ((buflen > 0) && 
2109                                (!isspace(buf[buflen])))
2110                                 buflen --;
2111                         if (buflen == 0) {
2112                                 xlline = 1;
2113                         }
2114                         else {
2115                                 mptr -= tbuflen - buflen;
2116                                 buf[buflen] = '\0';
2117                                 ch = '\r';
2118                         }
2119                 }
2120                 /* if we reach the outer bounds of our buffer, 
2121                    abort without respect what whe purge. */
2122                 if (xlline && 
2123                     ((isspace(ch)) || 
2124                      (buflen > SIZ - nllen - 2)))
2125                         ch = '\r';
2126
2127                 if (ch == '\r') {
2128                         memcpy (&buf[buflen], nl, nllen);
2129                         buflen += nllen;
2130                         buf[buflen] = '\0';
2131
2132                         if (client_write(buf, buflen) == -1)
2133                         {
2134                                 struct CitContext *CCC = CC;
2135                                 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2136                                 return;
2137                         }
2138                         *buf = '\0';
2139                         buflen = 0;
2140                         xlline = 0;
2141                 } else {
2142                         buf[buflen] = ch;
2143                         buflen++;
2144                 }
2145         }
2146         buf[buflen] = '\0';
2147         if (!IsEmptyStr(buf))
2148                 cprintf("%s%s", buf, nl);
2149 }
2150
2151 /*
2152  * Get a message off disk.  (returns om_* values found in msgbase.h)
2153  */
2154 int CtdlOutputPreLoadedMsg(
2155                 struct CtdlMessage *TheMessage,
2156                 int mode,               /* how would you like that message? */
2157                 int headers_only,       /* eschew the message body? */
2158                 int do_proto,           /* do Citadel protocol responses? */
2159                 int crlf,               /* Use CRLF newlines instead of LF? */
2160                 int flags               /* should the bessage be exported clean? */
2161 ) {
2162         struct CitContext *CCC = CC;
2163         int i;
2164         char *mptr = NULL;
2165         const char *nl; /* newline string */
2166         struct ma_info ma;
2167
2168         /* Buffers needed for RFC822 translation.  These are all filled
2169          * using functions that are bounds-checked, and therefore we can
2170          * make them substantially smaller than SIZ.
2171          */
2172         char suser[100];
2173         char luser[100];
2174         char fuser[100];
2175         char snode[100];
2176         char mid[100];
2177
2178         MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2179                    ((TheMessage == NULL) ? "NULL" : "not null"),
2180                    mode, headers_only, do_proto, crlf);
2181
2182         strcpy(mid, "unknown");
2183         nl = (crlf ? "\r\n" : "\n");
2184
2185         if (!CM_IsValidMsg(TheMessage)) {
2186                 MSGM_syslog(LOG_ERR,
2187                             "ERROR: invalid preloaded message for output\n");
2188                 cit_backtrace ();
2189                 return(om_no_such_msg);
2190         }
2191
2192         /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2193          * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2194          */
2195         if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2196                 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2197         }
2198                 
2199         /* Are we downloading a MIME component? */
2200         if (mode == MT_DOWNLOAD) {
2201                 if (TheMessage->cm_format_type != FMT_RFC822) {
2202                         if (do_proto)
2203                                 cprintf("%d This is not a MIME message.\n",
2204                                 ERROR + ILLEGAL_VALUE);
2205                 } else if (CCC->download_fp != NULL) {
2206                         if (do_proto) cprintf(
2207                                 "%d You already have a download open.\n",
2208                                 ERROR + RESOURCE_BUSY);
2209                 } else {
2210                         /* Parse the message text component */
2211                         mptr = TheMessage->cm_fields[eMesageText];
2212                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2213                         /* If there's no file open by this time, the requested
2214                          * section wasn't found, so print an error
2215                          */
2216                         if (CCC->download_fp == NULL) {
2217                                 if (do_proto) cprintf(
2218                                         "%d Section %s not found.\n",
2219                                         ERROR + FILE_NOT_FOUND,
2220                                         CCC->download_desired_section);
2221                         }
2222                 }
2223                 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2224         }
2225
2226         /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2227          * in a single server operation instead of opening a download file.
2228          */
2229         if (mode == MT_SPEW_SECTION) {
2230                 if (TheMessage->cm_format_type != FMT_RFC822) {
2231                         if (do_proto)
2232                                 cprintf("%d This is not a MIME message.\n",
2233                                 ERROR + ILLEGAL_VALUE);
2234                 } else {
2235                         /* Parse the message text component */
2236                         int found_it = 0;
2237
2238                         mptr = TheMessage->cm_fields[eMesageText];
2239                         mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2240                         /* If section wasn't found, print an error
2241                          */
2242                         if (!found_it) {
2243                                 if (do_proto) cprintf(
2244                                         "%d Section %s not found.\n",
2245                                         ERROR + FILE_NOT_FOUND,
2246                                         CCC->download_desired_section);
2247                         }
2248                 }
2249                 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2250         }
2251
2252         /* now for the user-mode message reading loops */
2253         if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2254
2255         /* Does the caller want to skip the headers? */
2256         if (headers_only == HEADERS_NONE) goto START_TEXT;
2257
2258         /* Tell the client which format type we're using. */
2259         if ( (mode == MT_CITADEL) && (do_proto) ) {
2260                 cprintf("type=%d\n", TheMessage->cm_format_type);
2261         }
2262
2263         /* nhdr=yes means that we're only displaying headers, no body */
2264         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2265            && ((mode == MT_CITADEL) || (mode == MT_MIME))
2266            && (do_proto)
2267            ) {
2268                 cprintf("nhdr=yes\n");
2269         }
2270
2271         if ((mode == MT_CITADEL) || (mode == MT_MIME)) 
2272                 OutputCtdlMsgHeaders(TheMessage, do_proto);
2273
2274
2275         /* begin header processing loop for RFC822 transfer format */
2276         strcpy(suser, "");
2277         strcpy(luser, "");
2278         strcpy(fuser, "");
2279         strcpy(snode, NODENAME);
2280         if (mode == MT_RFC822) 
2281                 OutputRFC822MsgHeaders(
2282                         TheMessage,
2283                         flags,
2284                         nl,
2285                         mid, sizeof(mid),
2286                         suser, sizeof(suser),
2287                         luser, sizeof(luser),
2288                         fuser, sizeof(fuser),
2289                         snode, sizeof(snode)
2290                         );
2291
2292
2293         for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2294                 suser[i] = tolower(suser[i]);
2295                 if (!isalnum(suser[i])) suser[i]='_';
2296         }
2297
2298         if (mode == MT_RFC822) {
2299                 if (!strcasecmp(snode, NODENAME)) {
2300                         safestrncpy(snode, FQDN, sizeof snode);
2301                 }
2302
2303                 /* Construct a fun message id */
2304                 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2305                 if (strchr(mid, '@')==NULL) {
2306                         cprintf("@%s", snode);
2307                 }
2308                 cprintf(">%s", nl);
2309
2310                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2311                         cprintf("From: \"----\" <x@x.org>%s", nl);
2312                 }
2313                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2314                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2315                 }
2316                 else if (!IsEmptyStr(fuser)) {
2317                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2318                 }
2319                 else {
2320                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2321                 }
2322
2323                 /* Blank line signifying RFC822 end-of-headers */
2324                 if (TheMessage->cm_format_type != FMT_RFC822) {
2325                         cprintf("%s", nl);
2326                 }
2327         }
2328
2329         /* end header processing loop ... at this point, we're in the text */
2330 START_TEXT:
2331         if (headers_only == HEADERS_FAST) goto DONE;
2332
2333         /* Tell the client about the MIME parts in this message */
2334         if (TheMessage->cm_format_type == FMT_RFC822) {
2335                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2336                         mptr = TheMessage->cm_fields[eMesageText];
2337                         memset(&ma, 0, sizeof(struct ma_info));
2338                         mime_parser(mptr, NULL,
2339                                 (do_proto ? *list_this_part : NULL),
2340                                 (do_proto ? *list_this_pref : NULL),
2341                                 (do_proto ? *list_this_suff : NULL),
2342                                 (void *)&ma, 1);
2343                 }
2344                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
2345                         Dump_RFC822HeadersBody(
2346                                 TheMessage,
2347                                 headers_only,
2348                                 flags,
2349                                 nl);
2350                         goto DONE;
2351                 }
2352         }
2353
2354         if (headers_only == HEADERS_ONLY) {
2355                 goto DONE;
2356         }
2357
2358         /* signify start of msg text */
2359         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2360                 if (do_proto) cprintf("text\n");
2361         }
2362
2363         if (TheMessage->cm_format_type == FMT_FIXED) 
2364                 DumpFormatFixed(
2365                         TheMessage,
2366                         mode,           /* how would you like that message? */
2367                         nl);
2368
2369         /* If the message on disk is format 0 (Citadel vari-format), we
2370          * output using the formatter at 80 columns.  This is the final output
2371          * form if the transfer format is RFC822, but if the transfer format
2372          * is Citadel proprietary, it'll still work, because the indentation
2373          * for new paragraphs is correct and the client will reformat the
2374          * message to the reader's screen width.
2375          */
2376         if (TheMessage->cm_format_type == FMT_CITADEL) {
2377                 mptr = TheMessage->cm_fields[eMesageText];
2378
2379                 if (mode == MT_MIME) {
2380                         cprintf("Content-type: text/x-citadel-variformat\n\n");
2381                 }
2382                 memfmout(mptr, nl);
2383         }
2384
2385         /* If the message on disk is format 4 (MIME), we've gotta hand it
2386          * off to the MIME parser.  The client has already been told that
2387          * this message is format 1 (fixed format), so the callback function
2388          * we use will display those parts as-is.
2389          */
2390         if (TheMessage->cm_format_type == FMT_RFC822) {
2391                 memset(&ma, 0, sizeof(struct ma_info));
2392
2393                 if (mode == MT_MIME) {
2394                         ma.use_fo_hooks = 0;
2395                         strcpy(ma.chosen_part, "1");
2396                         ma.chosen_pref = 9999;
2397                         ma.dont_decode = CCC->msg4_dont_decode;
2398                         mime_parser(mptr, NULL,
2399                                 *choose_preferred, *fixed_output_pre,
2400                                 *fixed_output_post, (void *)&ma, 1);
2401                         mime_parser(mptr, NULL,
2402                                 *output_preferred, NULL, NULL, (void *)&ma, 1);
2403                 }
2404                 else {
2405                         ma.use_fo_hooks = 1;
2406                         mime_parser(mptr, NULL,
2407                                 *fixed_output, *fixed_output_pre,
2408                                 *fixed_output_post, (void *)&ma, 0);
2409                 }
2410
2411         }
2412
2413 DONE:   /* now we're done */
2414         if (do_proto) cprintf("000\n");
2415         return(om_ok);
2416 }
2417
2418
2419 /*
2420  * display a message (mode 0 - Citadel proprietary)
2421  */
2422 void cmd_msg0(char *cmdbuf)
2423 {
2424         long msgid;
2425         int headers_only = HEADERS_ALL;
2426
2427         msgid = extract_long(cmdbuf, 0);
2428         headers_only = extract_int(cmdbuf, 1);
2429
2430         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2431         return;
2432 }
2433
2434
2435 /*
2436  * display a message (mode 2 - RFC822)
2437  */
2438 void cmd_msg2(char *cmdbuf)
2439 {
2440         long msgid;
2441         int headers_only = HEADERS_ALL;
2442
2443         msgid = extract_long(cmdbuf, 0);
2444         headers_only = extract_int(cmdbuf, 1);
2445
2446         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2447 }
2448
2449
2450
2451 /* 
2452  * display a message (mode 3 - IGnet raw format - internal programs only)
2453  */
2454 void cmd_msg3(char *cmdbuf)
2455 {
2456         long msgnum;
2457         struct CtdlMessage *msg = NULL;
2458         struct ser_ret smr;
2459
2460         if (CC->internal_pgm == 0) {
2461                 cprintf("%d This command is for internal programs only.\n",
2462                         ERROR + HIGHER_ACCESS_REQUIRED);
2463                 return;
2464         }
2465
2466         msgnum = extract_long(cmdbuf, 0);
2467         msg = CtdlFetchMessage(msgnum, 1);
2468         if (msg == NULL) {
2469                 cprintf("%d Message %ld not found.\n", 
2470                         ERROR + MESSAGE_NOT_FOUND, msgnum);
2471                 return;
2472         }
2473
2474         serialize_message(&smr, msg);
2475         CM_Free(msg);
2476
2477         if (smr.len == 0) {
2478                 cprintf("%d Unable to serialize message\n",
2479                         ERROR + INTERNAL_ERROR);
2480                 return;
2481         }
2482
2483         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2484         client_write((char *)smr.ser, (int)smr.len);
2485         free(smr.ser);
2486 }
2487
2488
2489
2490 /* 
2491  * Display a message using MIME content types
2492  */
2493 void cmd_msg4(char *cmdbuf)
2494 {
2495         long msgid;
2496         char section[64];
2497
2498         msgid = extract_long(cmdbuf, 0);
2499         extract_token(section, cmdbuf, 1, '|', sizeof section);
2500         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2501 }
2502
2503
2504
2505 /* 
2506  * Client tells us its preferred message format(s)
2507  */
2508 void cmd_msgp(char *cmdbuf)
2509 {
2510         if (!strcasecmp(cmdbuf, "dont_decode")) {
2511                 CC->msg4_dont_decode = 1;
2512                 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2513         }
2514         else {
2515                 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2516                 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2517         }
2518 }
2519
2520
2521 /*
2522  * Open a component of a MIME message as a download file 
2523  */
2524 void cmd_opna(char *cmdbuf)
2525 {
2526         long msgid;
2527         char desired_section[128];
2528
2529         msgid = extract_long(cmdbuf, 0);
2530         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2531         safestrncpy(CC->download_desired_section, desired_section,
2532                 sizeof CC->download_desired_section);
2533         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2534 }                       
2535
2536
2537 /*
2538  * Open a component of a MIME message and transmit it all at once
2539  */
2540 void cmd_dlat(char *cmdbuf)
2541 {
2542         long msgid;
2543         char desired_section[128];
2544
2545         msgid = extract_long(cmdbuf, 0);
2546         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2547         safestrncpy(CC->download_desired_section, desired_section,
2548                 sizeof CC->download_desired_section);
2549         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2550 }
2551
2552
2553 /*
2554  * Save one or more message pointers into a specified room
2555  * (Returns 0 for success, nonzero for failure)
2556  * roomname may be NULL to use the current room
2557  *
2558  * Note that the 'supplied_msg' field may be set to NULL, in which case
2559  * the message will be fetched from disk, by number, if we need to perform
2560  * replication checks.  This adds an additional database read, so if the
2561  * caller already has the message in memory then it should be supplied.  (Obviously
2562  * this mode of operation only works if we're saving a single message.)
2563  */
2564 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2565                         int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2566 ) {
2567         struct CitContext *CCC = CC;
2568         int i, j, unique;
2569         char hold_rm[ROOMNAMELEN];
2570         struct cdbdata *cdbfr;
2571         int num_msgs;
2572         long *msglist;
2573         long highest_msg = 0L;
2574
2575         long msgid = 0;
2576         struct CtdlMessage *msg = NULL;
2577
2578         long *msgs_to_be_merged = NULL;
2579         int num_msgs_to_be_merged = 0;
2580
2581         MSG_syslog(LOG_DEBUG,
2582                    "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2583                    roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2584         );
2585
2586         strcpy(hold_rm, CCC->room.QRname);
2587
2588         /* Sanity checks */
2589         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2590         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2591         if (num_newmsgs > 1) supplied_msg = NULL;
2592
2593         /* Now the regular stuff */
2594         if (CtdlGetRoomLock(&CCC->room,
2595            ((roomname != NULL) ? roomname : CCC->room.QRname) )
2596            != 0) {
2597                 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2598                 return(ERROR + ROOM_NOT_FOUND);
2599         }
2600
2601
2602         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2603         num_msgs_to_be_merged = 0;
2604
2605
2606         cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2607         if (cdbfr == NULL) {
2608                 msglist = NULL;
2609                 num_msgs = 0;
2610         } else {
2611                 msglist = (long *) cdbfr->ptr;
2612                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2613                 num_msgs = cdbfr->len / sizeof(long);
2614                 cdb_free(cdbfr);
2615         }
2616
2617
2618         /* Create a list of msgid's which were supplied by the caller, but do
2619          * not already exist in the target room.  It is absolutely taboo to
2620          * have more than one reference to the same message in a room.
2621          */
2622         for (i=0; i<num_newmsgs; ++i) {
2623                 unique = 1;
2624                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2625                         if (msglist[j] == newmsgidlist[i]) {
2626                                 unique = 0;
2627                         }
2628                 }
2629                 if (unique) {
2630                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2631                 }
2632         }
2633
2634         MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2635
2636         /*
2637          * Now merge the new messages
2638          */
2639         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2640         if (msglist == NULL) {
2641                 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2642                 free(msgs_to_be_merged);
2643                 return (ERROR + INTERNAL_ERROR);
2644         }
2645         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2646         num_msgs += num_msgs_to_be_merged;
2647
2648         /* Sort the message list, so all the msgid's are in order */
2649         num_msgs = sort_msglist(msglist, num_msgs);
2650
2651         /* Determine the highest message number */
2652         highest_msg = msglist[num_msgs - 1];
2653
2654         /* Write it back to disk. */
2655         cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2656                   msglist, (int)(num_msgs * sizeof(long)));
2657
2658         /* Free up the memory we used. */
2659         free(msglist);
2660
2661         /* Update the highest-message pointer and unlock the room. */
2662         CCC->room.QRhighest = highest_msg;
2663         CtdlPutRoomLock(&CCC->room);
2664
2665         /* Perform replication checks if necessary */
2666         if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2667                 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2668
2669                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2670                         msgid = msgs_to_be_merged[i];
2671         
2672                         if (supplied_msg != NULL) {
2673                                 msg = supplied_msg;
2674                         }
2675                         else {
2676                                 msg = CtdlFetchMessage(msgid, 0);
2677                         }
2678         
2679                         if (msg != NULL) {
2680                                 ReplicationChecks(msg);
2681                 
2682                                 /* If the message has an Exclusive ID, index that... */
2683                                 if (!CM_IsEmpty(msg, eExclusiveID)) {
2684                                         index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
2685                                 }
2686
2687                                 /* Free up the memory we may have allocated */
2688                                 if (msg != supplied_msg) {
2689                                         CM_Free(msg);
2690                                 }
2691                         }
2692         
2693                 }
2694         }
2695
2696         else {
2697                 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2698         }
2699
2700         /* Submit this room for processing by hooks */
2701         PerformRoomHooks(&CCC->room);
2702
2703         /* Go back to the room we were in before we wandered here... */
2704         CtdlGetRoom(&CCC->room, hold_rm);
2705
2706         /* Bump the reference count for all messages which were merged */
2707         if (!suppress_refcount_adj) {
2708                 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2709         }
2710
2711         /* Free up memory... */
2712         if (msgs_to_be_merged != NULL) {
2713                 free(msgs_to_be_merged);
2714         }
2715
2716         /* Return success. */
2717         return (0);
2718 }
2719
2720
2721 /*
2722  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2723  * a single message.
2724  */
2725 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2726                              int do_repl_check, struct CtdlMessage *supplied_msg)
2727 {
2728         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2729 }
2730
2731
2732
2733
2734 /*
2735  * Message base operation to save a new message to the message store
2736  * (returns new message number)
2737  *
2738  * This is the back end for CtdlSubmitMsg() and should not be directly
2739  * called by server-side modules.
2740  *
2741  */
2742 long send_message(struct CtdlMessage *msg) {
2743         struct CitContext *CCC = CC;
2744         long newmsgid;
2745         long retval;
2746         char msgidbuf[256];
2747         long msgidbuflen;
2748         struct ser_ret smr;
2749         int is_bigmsg = 0;
2750         char *holdM = NULL;
2751
2752         /* Get a new message number */
2753         newmsgid = get_new_message_number();
2754         msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2755                                (long unsigned int) time(NULL),
2756                                (long unsigned int) newmsgid,
2757                                config.c_fqdn
2758                 );
2759
2760         /* Generate an ID if we don't have one already */
2761         if (CM_IsEmpty(msg, emessageId)) {
2762                 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2763         }
2764
2765         /* If the message is big, set its body aside for storage elsewhere */
2766         if (!CM_IsEmpty(msg, eMesageText)) {
2767                 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
2768                         is_bigmsg = 1;
2769                         holdM = msg->cm_fields[eMesageText];
2770                         msg->cm_fields[eMesageText] = NULL;
2771                 }
2772         }
2773
2774         /* Serialize our data structure for storage in the database */  
2775         serialize_message(&smr, msg);
2776
2777         if (is_bigmsg) {
2778                 msg->cm_fields[eMesageText] = holdM;
2779         }
2780
2781         if (smr.len == 0) {
2782                 cprintf("%d Unable to serialize message\n",
2783                         ERROR + INTERNAL_ERROR);
2784                 return (-1L);
2785         }
2786
2787         /* Write our little bundle of joy into the message base */
2788         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2789                       smr.ser, smr.len) < 0) {
2790                 MSGM_syslog(LOG_ERR, "Can't store message\n");
2791                 retval = 0L;
2792         } else {
2793                 if (is_bigmsg) {
2794                         cdb_store(CDB_BIGMSGS,
2795                                   &newmsgid,
2796                                   (int)sizeof(long),
2797                                   holdM,
2798                                   (strlen(holdM) + 1)
2799                                 );
2800                 }
2801                 retval = newmsgid;
2802         }
2803
2804         /* Free the memory we used for the serialized message */
2805         free(smr.ser);
2806
2807         /* Return the *local* message ID to the caller
2808          * (even if we're storing an incoming network message)
2809          */
2810         return(retval);
2811 }
2812
2813
2814
2815 /*
2816  * Serialize a struct CtdlMessage into the format used on disk and network.
2817  * 
2818  * This function loads up a "struct ser_ret" (defined in server.h) which
2819  * contains the length of the serialized message and a pointer to the
2820  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2821  */
2822 void serialize_message(struct ser_ret *ret,             /* return values */
2823                        struct CtdlMessage *msg) /* unserialized msg */
2824 {
2825         struct CitContext *CCC = CC;
2826         size_t wlen, fieldlen;
2827         int i;
2828         long lengths[NDiskFields];
2829         
2830         memset(lengths, 0, sizeof(lengths));
2831
2832         /*
2833          * Check for valid message format
2834          */
2835         if (CM_IsValidMsg(msg) == 0) {
2836                 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2837                 ret->len = 0;
2838                 ret->ser = NULL;
2839                 return;
2840         }
2841
2842         ret->len = 3;
2843         for (i=0; i < NDiskFields; ++i)
2844                 if (msg->cm_fields[FieldOrder[i]] != NULL)
2845                 {
2846                         lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
2847                         ret->len += lengths[i] + 2;
2848                 }
2849
2850         ret->ser = malloc(ret->len);
2851         if (ret->ser == NULL) {
2852                 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2853                            (long)ret->len, strerror(errno));
2854                 ret->len = 0;
2855                 ret->ser = NULL;
2856                 return;
2857         }
2858
2859         ret->ser[0] = 0xFF;
2860         ret->ser[1] = msg->cm_anon_type;
2861         ret->ser[2] = msg->cm_format_type;
2862         wlen = 3;
2863
2864         for (i=0; i < NDiskFields; ++i)
2865                 if (msg->cm_fields[FieldOrder[i]] != NULL)
2866                 {
2867                         fieldlen = lengths[i];
2868                         ret->ser[wlen++] = (char)FieldOrder[i];
2869
2870                         memcpy(&ret->ser[wlen],
2871                                msg->cm_fields[FieldOrder[i]],
2872                                fieldlen+1);
2873
2874                         wlen = wlen + fieldlen + 1;
2875                 }
2876
2877         if (ret->len != wlen) {
2878                 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2879                            (long)ret->len, (long)wlen);
2880         }
2881
2882         return;
2883 }
2884
2885
2886 /*
2887  * Check to see if any messages already exist in the current room which
2888  * carry the same Exclusive ID as this one.  If any are found, delete them.
2889  */
2890 void ReplicationChecks(struct CtdlMessage *msg) {
2891         struct CitContext *CCC = CC;
2892         long old_msgnum = (-1L);
2893
2894         if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
2895
2896         MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2897                    CCC->room.QRname);
2898
2899         /* No exclusive id?  Don't do anything. */
2900         if (msg == NULL) return;
2901         if (CM_IsEmpty(msg, eExclusiveID)) return;
2902
2903         /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2904           msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
2905
2906         old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
2907         if (old_msgnum > 0L) {
2908                 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2909                 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
2910         }
2911 }
2912
2913
2914
2915 /*
2916  * Save a message to disk and submit it into the delivery system.
2917  */
2918 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
2919                    struct recptypes *recps,     /* recipients (if mail) */
2920                    const char *force,           /* force a particular room? */
2921                    int flags                    /* should the message be exported clean? */
2922         )
2923 {
2924         char submit_filename[128];
2925         char hold_rm[ROOMNAMELEN];
2926         char actual_rm[ROOMNAMELEN];
2927         char force_room[ROOMNAMELEN];
2928         char content_type[SIZ];                 /* We have to learn this */
2929         char recipient[SIZ];
2930         const char *room;
2931         long newmsgid;
2932         const char *mptr = NULL;
2933         struct ctdluser userbuf;
2934         int a, i;
2935         struct MetaData smi;
2936         FILE *network_fp = NULL;
2937         static int seqnum = 1;
2938         struct CtdlMessage *imsg = NULL;
2939         char *instr = NULL;
2940         size_t instr_alloc = 0;
2941         struct ser_ret smr;
2942         char *hold_R, *hold_D;
2943         char *collected_addresses = NULL;
2944         struct addresses_to_be_filed *aptr = NULL;
2945         StrBuf *saved_rfc822_version = NULL;
2946         int qualified_for_journaling = 0;
2947         CitContext *CCC = MyContext();
2948         char bounce_to[1024] = "";
2949         int rv = 0;
2950
2951         MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2952         if (CM_IsValidMsg(msg) == 0) return(-1);        /* self check */
2953
2954         /* If this message has no timestamp, we take the liberty of
2955          * giving it one, right now.
2956          */
2957         if (CM_IsEmpty(msg, eTimestamp)) {
2958                 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2959         }
2960
2961         /* If this message has no path, we generate one.
2962          */
2963         if (CM_IsEmpty(msg, eMessagePath)) {
2964                 if (!CM_IsEmpty(msg, eAuthor)) {
2965                         CM_CopyField(msg, eMessagePath, eAuthor);
2966                         for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2967                                 if (isspace(msg->cm_fields[eMessagePath][a])) {
2968                                         msg->cm_fields[eMessagePath][a] = ' ';
2969                                 }
2970                         }
2971                 }
2972                 else {
2973                         CM_SetField(msg, eMessagePath, HKEY("unknown"));
2974                 }
2975         }
2976
2977         if (force == NULL) {
2978                 force_room[0] = '\0';
2979         }
2980         else {
2981                 strcpy(force_room, force);
2982         }
2983
2984         /* Learn about what's inside, because it's what's inside that counts */
2985         if (CM_IsEmpty(msg, eMesageText)) {
2986                 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2987                 return(-2);
2988         }
2989
2990         switch (msg->cm_format_type) {
2991         case 0:
2992                 strcpy(content_type, "text/x-citadel-variformat");
2993                 break;
2994         case 1:
2995                 strcpy(content_type, "text/plain");
2996                 break;
2997         case 4:
2998                 strcpy(content_type, "text/plain");
2999                 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3000                 if (mptr != NULL) {
3001                         char *aptr;
3002                         safestrncpy(content_type, &mptr[13], sizeof content_type);
3003                         striplt(content_type);
3004                         aptr = content_type;
3005                         while (!IsEmptyStr(aptr)) {
3006                                 if ((*aptr == ';')
3007                                     || (*aptr == ' ')
3008                                     || (*aptr == 13)
3009                                     || (*aptr == 10)) {
3010                                         *aptr = 0;
3011                                 }
3012                                 else aptr++;
3013                         }
3014                 }
3015         }
3016
3017         /* Goto the correct room */
3018         room = (recps) ? CCC->room.QRname : SENTITEMS;
3019         MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3020         strcpy(hold_rm, CCC->room.QRname);
3021         strcpy(actual_rm, CCC->room.QRname);
3022         if (recps != NULL) {
3023                 strcpy(actual_rm, SENTITEMS);
3024         }
3025
3026         /* If the user is a twit, move to the twit room for posting */
3027         if (TWITDETECT) {
3028                 if (CCC->user.axlevel == AxProbU) {
3029                         strcpy(hold_rm, actual_rm);
3030                         strcpy(actual_rm, config.c_twitroom);
3031                         MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3032                 }
3033         }
3034
3035         /* ...or if this message is destined for Aide> then go there. */
3036         if (!IsEmptyStr(force_room)) {
3037                 strcpy(actual_rm, force_room);
3038         }
3039
3040         MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3041         if (strcasecmp(actual_rm, CCC->room.QRname)) {
3042                 /* CtdlGetRoom(&CCC->room, actual_rm); */
3043                 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3044         }
3045
3046         /*
3047          * If this message has no O (room) field, generate one.
3048          */
3049         if (CM_IsEmpty(msg, eOriginalRoom)) {
3050                 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3051         }
3052
3053         /* Perform "before save" hooks (aborting if any return nonzero) */
3054         MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3055         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3056
3057         /*
3058          * If this message has an Exclusive ID, and the room is replication
3059          * checking enabled, then do replication checks.
3060          */
3061         if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3062                 ReplicationChecks(msg);
3063         }
3064
3065         /* Save it to disk */
3066         MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3067         newmsgid = send_message(msg);
3068         if (newmsgid <= 0L) return(-5);
3069
3070         /* Write a supplemental message info record.  This doesn't have to
3071          * be a critical section because nobody else knows about this message
3072          * yet.
3073          */
3074         MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3075         memset(&smi, 0, sizeof(struct MetaData));
3076         smi.meta_msgnum = newmsgid;
3077         smi.meta_refcount = 0;
3078         safestrncpy(smi.meta_content_type, content_type,
3079                     sizeof smi.meta_content_type);
3080
3081         /*
3082          * Measure how big this message will be when rendered as RFC822.
3083          * We do this for two reasons:
3084          * 1. We need the RFC822 length for the new metadata record, so the
3085          *    POP and IMAP services don't have to calculate message lengths
3086          *    while the user is waiting (multiplied by potentially hundreds
3087          *    or thousands of messages).
3088          * 2. If journaling is enabled, we will need an RFC822 version of the
3089          *    message to attach to the journalized copy.
3090          */
3091         if (CCC->redirect_buffer != NULL) {
3092                 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3093                 abort();
3094         }
3095         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3096         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3097         smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3098         saved_rfc822_version = CCC->redirect_buffer;
3099         CCC->redirect_buffer = NULL;
3100
3101         PutMetaData(&smi);
3102
3103         /* Now figure out where to store the pointers */
3104         MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3105
3106         /* If this is being done by the networker delivering a private
3107          * message, we want to BYPASS saving the sender's copy (because there
3108          * is no local sender; it would otherwise go to the Trashcan).
3109          */
3110         if ((!CCC->internal_pgm) || (recps == NULL)) {
3111                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3112                         MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3113                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3114                 }
3115         }
3116
3117         /* For internet mail, drop a copy in the outbound queue room */
3118         if ((recps != NULL) && (recps->num_internet > 0)) {
3119                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3120         }
3121
3122         /* If other rooms are specified, drop them there too. */
3123         if ((recps != NULL) && (recps->num_room > 0))
3124                 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3125                         extract_token(recipient, recps->recp_room, i,
3126                                       '|', sizeof recipient);
3127                         MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3128                         CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3129                 }
3130
3131         /* Bump this user's messages posted counter. */
3132         MSGM_syslog(LOG_DEBUG, "Updating user\n");
3133         CtdlGetUserLock(&CCC->user, CCC->curr_user);
3134         CCC->user.posted = CCC->user.posted + 1;
3135         CtdlPutUserLock(&CCC->user);
3136
3137         /* Decide where bounces need to be delivered */
3138         if ((recps != NULL) && (recps->bounce_to != NULL)) {
3139                 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3140         }
3141         else if (CCC->logged_in) {
3142                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3143         }
3144         else {
3145                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3146         }
3147
3148         /* If this is private, local mail, make a copy in the
3149          * recipient's mailbox and bump the reference count.
3150          */
3151         if ((recps != NULL) && (recps->num_local > 0))
3152                 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3153                         long recipientlen;
3154                         recipientlen = extract_token(recipient,
3155                                                      recps->recp_local, i,
3156                                                      '|', sizeof recipient);
3157                         MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3158                                recipient);
3159                         if (CtdlGetUser(&userbuf, recipient) == 0) {
3160                                 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3161                                 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3162                                 CtdlBumpNewMailCounter(userbuf.usernum);
3163                                 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3164                                         /* Generate a instruction message for the Funambol notification
3165                                          * server, in the same style as the SMTP queue
3166                                          */
3167                                         long instrlen;
3168                                         instr_alloc = 1024;
3169                                         instr = malloc(instr_alloc);
3170                                         instrlen = snprintf(
3171                                                 instr, instr_alloc,
3172                                                 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3173                                                 "bounceto|%s\n",
3174                                                 SPOOLMIME,
3175                                                 newmsgid,
3176                                                 (long)time(NULL), //todo: time() is expensive!
3177                                                 bounce_to
3178                                                 );
3179                                 
3180                                         imsg = malloc(sizeof(struct CtdlMessage));
3181                                         memset(imsg, 0, sizeof(struct CtdlMessage));
3182                                         imsg->cm_magic = CTDLMESSAGE_MAGIC;
3183                                         imsg->cm_anon_type = MES_NORMAL;
3184                                         imsg->cm_format_type = FMT_RFC822;
3185                                         CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
3186                                         CM_SetField(imsg, eAuthor, HKEY("Citadel"));
3187                                         CM_SetField(imsg, eJournal, HKEY("do not journal"));
3188                                         CM_SetAsField(imsg, eMesageText, &instr, instrlen);
3189                                         CM_SetField(imsg, eExtnotify, recipient, recipientlen);
3190                                         CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3191                                         CM_Free(imsg);
3192                                 }
3193                         }
3194                         else {
3195                                 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3196                                 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3197                         }
3198                 }
3199
3200         /* Perform "after save" hooks */
3201         MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3202
3203         CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
3204         PerformMessageHooks(msg, EVT_AFTERSAVE);
3205         CM_FlushField(msg, eVltMsgNum);
3206
3207         /* For IGnet mail, we have to save a new copy into the spooler for
3208          * each recipient, with the R and D fields set to the recipient and
3209          * destination-node.  This has two ugly side effects: all other
3210          * recipients end up being unlisted in this recipient's copy of the
3211          * message, and it has to deliver multiple messages to the same
3212          * node.  We'll revisit this again in a year or so when everyone has
3213          * a network spool receiver that can handle the new style messages.
3214          */
3215         if ((recps != NULL) && (recps->num_ignet > 0))
3216                 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3217                         extract_token(recipient, recps->recp_ignet, i,
3218                                       '|', sizeof recipient);
3219
3220                         hold_R = msg->cm_fields[eRecipient];
3221                         hold_D = msg->cm_fields[eDestination];
3222                         msg->cm_fields[eRecipient] = malloc(SIZ);
3223                         msg->cm_fields[eDestination] = malloc(128);
3224                         extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3225                         extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3226                 
3227                         serialize_message(&smr, msg);
3228                         if (smr.len > 0) {
3229                                 snprintf(submit_filename, sizeof submit_filename,
3230                                          "%s/netmail.%04lx.%04x.%04x",
3231                                          ctdl_netin_dir,
3232                                          (long) getpid(), CCC->cs_pid, ++seqnum);
3233                                 network_fp = fopen(submit_filename, "wb+");
3234                                 if (network_fp != NULL) {
3235                                         rv = fwrite(smr.ser, smr.len, 1, network_fp);
3236                                         if (rv == -1) {
3237                                                 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3238                                                            strerror(errno));
3239                                         }
3240                                         fclose(network_fp);
3241                                 }
3242                                 free(smr.ser);
3243                         }
3244
3245                         free(msg->cm_fields[eRecipient]);
3246                         free(msg->cm_fields[eDestination]);
3247                         msg->cm_fields[eRecipient] = hold_R;
3248                         msg->cm_fields[eDestination] = hold_D;
3249                 }
3250
3251         /* Go back to the room we started from */
3252         MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3253         if (strcasecmp(hold_rm, CCC->room.QRname))
3254                 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3255
3256         /* For internet mail, generate delivery instructions.
3257          * Yes, this is recursive.  Deal with it.  Infinite recursion does
3258          * not happen because the delivery instructions message does not
3259          * contain a recipient.
3260          */
3261         if ((recps != NULL) && (recps->num_internet > 0)) {
3262                 StrBuf *SpoolMsg = NewStrBuf();
3263                 long nTokens;
3264
3265                 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3266
3267                 StrBufPrintf(SpoolMsg,
3268                              "Content-type: "SPOOLMIME"\n"
3269                              "\n"
3270                              "msgid|%ld\n"
3271                              "submitted|%ld\n"
3272                              "bounceto|%s\n",
3273                              newmsgid,
3274                              (long)time(NULL),
3275                              bounce_to);
3276
3277                 if (recps->envelope_from != NULL) {
3278                         StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3279                         StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3280                         StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3281                 }
3282                 if (recps->sending_room != NULL) {
3283                         StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3284                         StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3285                         StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3286                 }
3287
3288                 nTokens = num_tokens(recps->recp_internet, '|');
3289                 for (i = 0; i < nTokens; i++) {
3290                         long len;
3291                         len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3292                         if (len > 0) {
3293                                 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3294                                 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3295                                 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3296                         }
3297                 }
3298
3299                 imsg = malloc(sizeof(struct CtdlMessage));
3300                 memset(imsg, 0, sizeof(struct CtdlMessage));
3301                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3302                 imsg->cm_anon_type = MES_NORMAL;
3303                 imsg->cm_format_type = FMT_RFC822;
3304                 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3305                 imsg->cm_fields[eAuthor] = strdup("Citadel");
3306                 imsg->cm_fields[eJournal] = strdup("do not journal");
3307                 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg);  /* imsg owns this memory now */
3308                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3309                 CM_Free(imsg);
3310         }
3311
3312         /*
3313          * Any addresses to harvest for someone's address book?
3314          */
3315         if ( (CCC->logged_in) && (recps != NULL) ) {
3316                 collected_addresses = harvest_collected_addresses(msg);
3317         }
3318
3319         if (collected_addresses != NULL) {
3320                 aptr = (struct addresses_to_be_filed *)
3321                         malloc(sizeof(struct addresses_to_be_filed));
3322                 CtdlMailboxName(actual_rm, sizeof actual_rm,
3323                                 &CCC->user, USERCONTACTSROOM);
3324                 aptr->roomname = strdup(actual_rm);
3325                 aptr->collected_addresses = collected_addresses;
3326                 begin_critical_section(S_ATBF);
3327                 aptr->next = atbf;
3328                 atbf = aptr;
3329                 end_critical_section(S_ATBF);
3330         }
3331
3332         /*
3333          * Determine whether this message qualifies for journaling.
3334          */
3335         if (!CM_IsEmpty(msg, eJournal)) {
3336                 qualified_for_journaling = 0;
3337         }
3338         else {
3339                 if (recps == NULL) {
3340                         qualified_for_journaling = config.c_journal_pubmsgs;
3341                 }
3342                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3343                         qualified_for_journaling = config.c_journal_email;
3344                 }
3345                 else {
3346                         qualified_for_journaling = config.c_journal_pubmsgs;
3347                 }
3348         }
3349
3350         /*
3351          * Do we have to perform journaling?  If so, hand off the saved
3352          * RFC822 version will be handed off to the journaler for background
3353          * submit.  Otherwise, we have to free the memory ourselves.
3354          */
3355         if (saved_rfc822_version != NULL) {
3356                 if (qualified_for_journaling) {
3357                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3358                 }
3359                 else {
3360                         FreeStrBuf(&saved_rfc822_version);
3361                 }
3362         }
3363
3364         /* Done. */
3365         return(newmsgid);
3366 }
3367
3368
3369 /*
3370  * Convenience function for generating small administrative messages.
3371  */
3372 void quickie_message(const char *from,
3373                      const char *fromaddr,
3374                      const char *to,
3375                      char *room,
3376                      const char *text, 
3377                      int format_type,
3378                      const char *subject)
3379 {
3380         struct CtdlMessage *msg;
3381         struct recptypes *recp = NULL;
3382
3383         msg = malloc(sizeof(struct CtdlMessage));
3384         memset(msg, 0, sizeof(struct CtdlMessage));
3385         msg->cm_magic = CTDLMESSAGE_MAGIC;
3386         msg->cm_anon_type = MES_NORMAL;
3387         msg->cm_format_type = format_type;
3388
3389         if (from != NULL) {
3390                 msg->cm_fields[eAuthor] = strdup(from);
3391         }
3392         else if (fromaddr != NULL) {
3393                 msg->cm_fields[eAuthor] = strdup(fromaddr);
3394                 if (strchr(msg->cm_fields[eAuthor], '@')) {
3395                         *strchr(msg->cm_fields[eAuthor], '@') = 0;
3396                 }
3397         }
3398         else {
3399                 msg->cm_fields[eAuthor] = strdup("Citadel");
3400         }
3401
3402         if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3403         if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3404         msg->cm_fields[eNodeName] = strdup(NODENAME);
3405         if (to != NULL) {
3406                 msg->cm_fields[eRecipient] = strdup(to);
3407                 recp = validate_recipients(to, NULL, 0);
3408         }
3409         if (subject != NULL) {
3410                 msg->cm_fields[eMsgSubject] = strdup(subject);
3411         }
3412         msg->cm_fields[eMesageText] = strdup(text);
3413
3414         CtdlSubmitMsg(msg, recp, room, 0);
3415         CM_Free(msg);
3416         if (recp != NULL) free_recipients(recp);
3417 }
3418
3419 void flood_protect_quickie_message(const char *from,
3420                                    const char *fromaddr,
3421                                    const char *to,
3422                                    char *room,
3423                                    const char *text, 
3424                                    int format_type,
3425                                    const char *subject,
3426                                    int nCriterions,
3427                                    const char **CritStr,
3428                                    long *CritStrLen,
3429                                    long ccid,
3430                                    long ioid,
3431                                    time_t NOW)
3432 {
3433         int i;
3434         u_char rawdigest[MD5_DIGEST_LEN];
3435         struct MD5Context md5context;
3436         StrBuf *guid;
3437         char timestamp[64];
3438         long tslen;
3439         time_t tsday = NOW / (8*60*60); /* just care for a day... */
3440
3441         tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3442         MD5Init(&md5context);
3443
3444         for (i = 0; i < nCriterions; i++)
3445                 MD5Update(&md5context,
3446                           (const unsigned char*)CritStr[i], CritStrLen[i]);
3447         MD5Update(&md5context,
3448                   (const unsigned char*)timestamp, tslen);
3449         MD5Final(rawdigest, &md5context);
3450
3451         guid = NewStrBufPlain(NULL,
3452                               MD5_DIGEST_LEN * 2 + 12);
3453         StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3454         StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3455         if (StrLength(guid) > 40)
3456                 StrBufCutAt(guid, 40, NULL);
3457
3458         if (CheckIfAlreadySeen("FPAideMessage",
3459                                guid,
3460                                NOW,
3461                                tsday,
3462                                eUpdate,
3463                                ccid,
3464                                ioid)!= 0)
3465         {
3466                 FreeStrBuf(&guid);
3467                 /* yes, we did. flood protection kicks in. */
3468                 syslog(LOG_DEBUG,
3469                        "not sending message again\n");
3470                 return;
3471         }
3472         FreeStrBuf(&guid);
3473         /* no, this message isn't sent recently; go ahead. */
3474         quickie_message(from,
3475                         fromaddr,
3476                         to,
3477                         room,
3478                         text, 
3479                         format_type,
3480                         subject);
3481 }
3482
3483
3484 /*
3485  * Back end function used by CtdlMakeMessage() and similar functions
3486  */
3487 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3488                                long tlen,
3489                                size_t maxlen,           /* maximum message length */
3490                                StrBuf *exist,           /* if non-null, append to it;
3491                                                            exist is ALWAYS freed  */
3492                                int crlf,                /* CRLF newlines instead of LF */
3493                                int *sock                /* socket handle or 0 for this session's client socket */
3494         ) 
3495 {
3496         StrBuf *Message;
3497         StrBuf *LineBuf;
3498         int flushing = 0;
3499         int finished = 0;
3500         int dotdot = 0;
3501
3502         LineBuf = NewStrBufPlain(NULL, SIZ);
3503         if (exist == NULL) {
3504                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3505         }
3506         else {
3507                 Message = NewStrBufDup(exist);
3508         }
3509
3510         /* Do we need to change leading ".." to "." for SMTP escaping? */
3511         if ((tlen == 1) && (*terminator == '.')) {
3512                 dotdot = 1;
3513         }
3514
3515         /* read in the lines of message text one by one */
3516         do {
3517                 if (sock != NULL) {
3518                         if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3519                             (*sock == -1))
3520                                 finished = 1;
3521                 }
3522                 else {
3523                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3524                 }
3525                 if ((StrLength(LineBuf) == tlen) && 
3526                     (!strcmp(ChrPtr(LineBuf), terminator)))
3527                         finished = 1;
3528
3529                 if ( (!flushing) && (!finished) ) {
3530                         if (crlf) {
3531                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3532                         }
3533                         else {
3534                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3535                         }
3536                         
3537                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3538                         if ((dotdot) &&
3539                             (StrLength(LineBuf) == 2) && 
3540                             (!strcmp(ChrPtr(LineBuf), "..")))
3541                         {
3542                                 StrBufCutLeft(LineBuf, 1);
3543                         }
3544                         
3545                         StrBufAppendBuf(Message, LineBuf, 0);
3546                 }
3547
3548                 /* if we've hit the max msg length, flush the rest */
3549                 if (StrLength(Message) >= maxlen) flushing = 1;
3550
3551         } while (!finished);
3552         FreeStrBuf(&LineBuf);
3553         return Message;
3554 }
3555
3556 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3557 {
3558         if (*Msg == NULL)
3559                 return;
3560         FreeStrBuf(&(*Msg)->MsgBuf);
3561
3562         free(*Msg);
3563         *Msg = NULL;
3564 }
3565
3566 ReadAsyncMsg *NewAsyncMsg(const char *terminator,       /* token signalling EOT */
3567                           long tlen,
3568                           size_t maxlen,                /* maximum message length */
3569                           size_t expectlen,             /* if we expect a message, how long should it be? */
3570                           StrBuf *exist,                /* if non-null, append to it;
3571                                                            exist is ALWAYS freed  */
3572                           long eLen,                    /* length of exist */
3573                           int crlf                      /* CRLF newlines instead of LF */
3574         )
3575 {
3576         ReadAsyncMsg *NewMsg;
3577
3578         NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3579         memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3580
3581         if (exist == NULL) {
3582                 long len;
3583
3584                 if (expectlen == 0) {
3585                         len = 4 * SIZ;
3586                 }
3587                 else {
3588                         len = expectlen + 10;
3589                 }
3590                 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3591         }
3592         else {
3593                 NewMsg->MsgBuf = NewStrBufDup(exist);
3594         }
3595         /* Do we need to change leading ".." to "." for SMTP escaping? */
3596         if ((tlen == 1) && (*terminator == '.')) {
3597                 NewMsg->dodot = 1;
3598         }
3599
3600         NewMsg->terminator = terminator;
3601         NewMsg->tlen = tlen;
3602
3603         NewMsg->maxlen = maxlen;
3604
3605         NewMsg->crlf = crlf;
3606
3607         return NewMsg;
3608 }
3609
3610 /*
3611  * Back end function used by CtdlMakeMessage() and similar functions
3612  */
3613 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3614 {
3615         ReadAsyncMsg *ReadMsg;
3616         int MsgFinished = 0;
3617         eReadState Finished = eMustReadMore;
3618
3619 #ifdef BIGBAD_IODBG
3620         char fn [SIZ];
3621         FILE *fd;
3622         const char *pch = ChrPtr(IO->SendBuf.Buf);
3623         const char *pchh = IO->SendBuf.ReadWritePointer;
3624         long nbytes;
3625         
3626         if (pchh == NULL)
3627                 pchh = pch;
3628         
3629         nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3630         snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3631                  ((CitContext*)(IO->CitContext))->ServiceName,
3632                  IO->SendBuf.fd);
3633         
3634         fd = fopen(fn, "a+");
3635 #endif
3636
3637         ReadMsg = IO->ReadMsg;
3638
3639         /* read in the lines of message text one by one */
3640         do {
3641                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3642                 
3643                 switch (Finished) {
3644                 case eMustReadMore: /// read new from socket... 
3645 #ifdef BIGBAD_IODBG
3646                         if (IO->RecvBuf.ReadWritePointer != NULL) {
3647                                 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3648                                 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3649                                 
3650                                 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3651                         
3652                                 fprintf(fd, "]\n");
3653                         } else {
3654                                 fprintf(fd, "BufferEmpty! \n");
3655                         }
3656                         fclose(fd);
3657 #endif
3658                         return Finished;
3659                     break;
3660                 case eBufferNotEmpty: /* shouldn't happen... */
3661                 case eReadSuccess: /// done for now...
3662                     break;
3663                 case eReadFail: /// WHUT?
3664                     ///todo: shut down! 
3665                         break;
3666                 }
3667             
3668
3669                 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && 
3670                     (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3671                         MsgFinished = 1;
3672 #ifdef BIGBAD_IODBG
3673                         fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3674 #endif
3675                 }
3676                 else if (!ReadMsg->flushing) {
3677
3678 #ifdef BIGBAD_IODBG
3679                         fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3680 #endif
3681
3682                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3683                         if ((ReadMsg->dodot) &&
3684                             (StrLength(IO->IOBuf) == 2) &&  /* TODO: do we just unescape lines with two dots or any line? */
3685                             (!strcmp(ChrPtr(IO->IOBuf), "..")))
3686                         {
3687 #ifdef BIGBAD_IODBG
3688                                 fprintf(fd, "UnEscaped!\n");
3689 #endif
3690                                 StrBufCutLeft(IO->IOBuf, 1);
3691                         }
3692
3693                         if (ReadMsg->crlf) {
3694                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3695                         }
3696                         else {
3697                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3698                         }
3699
3700                         StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3701                 }
3702
3703                 /* if we've hit the max msg length, flush the rest */
3704                 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3705
3706         } while (!MsgFinished);
3707
3708 #ifdef BIGBAD_IODBG
3709         fprintf(fd, "Done with reading; %s.\n, ",
3710                 (MsgFinished)?"Message Finished": "FAILED");
3711         fclose(fd);
3712 #endif
3713         if (MsgFinished)
3714                 return eReadSuccess;
3715         else 
3716                 return eAbort;
3717 }
3718
3719
3720 /*
3721  * Back end function used by CtdlMakeMessage() and similar functions
3722  */
3723 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3724                           long tlen,
3725                           size_t maxlen,                /* maximum message length */
3726                           StrBuf *exist,                /* if non-null, append to it;
3727                                                    exist is ALWAYS freed  */
3728                           int crlf,             /* CRLF newlines instead of LF */
3729                           int *sock             /* socket handle or 0 for this session's client socket */
3730         ) 
3731 {
3732         StrBuf *Message;
3733
3734         Message = CtdlReadMessageBodyBuf(terminator,
3735                                          tlen,
3736                                          maxlen,
3737                                          exist,
3738                                          crlf,
3739                                          sock);
3740         if (Message == NULL)
3741                 return NULL;
3742         else
3743                 return SmashStrBuf(&Message);
3744 }
3745
3746
3747 /*
3748  * Build a binary message to be saved on disk.
3749  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3750  * will become part of the message.  This means you are no longer
3751  * responsible for managing that memory -- it will be freed along with
3752  * the rest of the fields when CM_Free() is called.)
3753  */
3754
3755 struct CtdlMessage *CtdlMakeMessage(
3756         struct ctdluser *author,        /* author's user structure */
3757         char *recipient,                /* NULL if it's not mail */
3758         char *recp_cc,                  /* NULL if it's not mail */
3759         char *room,                     /* room where it's going */
3760         int type,                       /* see MES_ types in header file */
3761         int format_type,                /* variformat, plain text, MIME... */
3762         char *fake_name,                /* who we're masquerading as */
3763         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3764         char *subject,                  /* Subject (optional) */
3765         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3766         char *preformatted_text,        /* ...or NULL to read text from client */
3767         char *references                /* Thread references */
3768         ) {
3769         char dest_node[256];
3770         char buf[1024];
3771         struct CtdlMessage *msg;
3772         StrBuf *FakeAuthor;
3773         StrBuf *FakeEncAuthor = NULL;
3774
3775         msg = malloc(sizeof(struct CtdlMessage));
3776         memset(msg, 0, sizeof(struct CtdlMessage));
3777         msg->cm_magic = CTDLMESSAGE_MAGIC;
3778         msg->cm_anon_type = type;
3779         msg->cm_format_type = format_type;
3780
3781         /* Don't confuse the poor folks if it's not routed mail. */
3782         strcpy(dest_node, "");
3783
3784         if (recipient != NULL) striplt(recipient);
3785         if (recp_cc != NULL) striplt(recp_cc);
3786
3787         /* Path or Return-Path */
3788         if (my_email == NULL) my_email = "";
3789
3790         if (!IsEmptyStr(my_email)) {
3791                 msg->cm_fields[eMessagePath] = strdup(my_email);
3792         }
3793         else {
3794                 snprintf(buf, sizeof buf, "%s", author->fullname);
3795                 msg->cm_fields[eMessagePath] = strdup(buf);
3796         }
3797         convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3798
3799         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3800         msg->cm_fields[eTimestamp] = strdup(buf);
3801
3802         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3803                 FakeAuthor = NewStrBufPlain (fake_name, -1);
3804         }
3805         else {
3806                 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3807         }
3808         StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3809         msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
3810         FreeStrBuf(&FakeAuthor);
3811
3812         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3813                 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
3814         }
3815         else {
3816                 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
3817         }
3818
3819         msg->cm_fields[eNodeName] = strdup(NODENAME);           /* nodename */
3820         msg->cm_fields[eHumanNode] = strdup(HUMANNODE);         /* hnodename */
3821
3822         if ((recipient != NULL) && (recipient[0] != 0)) {
3823                 msg->cm_fields[eRecipient] = strdup(recipient);
3824         }
3825         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3826                 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
3827         }
3828         if (dest_node[0] != 0) {
3829                 msg->cm_fields[eDestination] = strdup(dest_node);
3830         }
3831
3832         if (!IsEmptyStr(my_email)) {
3833                 msg->cm_fields[erFc822Addr] = strdup(my_email);
3834         }
3835         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3836                 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
3837         }
3838
3839         if (subject != NULL) {
3840                 long length;
3841                 striplt(subject);
3842                 length = strlen(subject);
3843                 if (length > 0) {
3844                         long i;
3845                         long IsAscii;
3846                         IsAscii = -1;
3847                         i = 0;
3848                         while ((subject[i] != '\0') &&
3849                                (IsAscii = isascii(subject[i]) != 0 ))
3850                                 i++;
3851                         if (IsAscii != 0)
3852                                 msg->cm_fields[eMsgSubject] = strdup(subject);
3853                         else /* ok, we've got utf8 in the string. */
3854                         {
3855                                 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
3856                         }
3857
3858                 }
3859         }
3860
3861         if (supplied_euid != NULL) {
3862                 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
3863         }
3864
3865         if ((references != NULL) && (!IsEmptyStr(references))) {
3866                 if (msg->cm_fields[eWeferences] != NULL)
3867                         free(msg->cm_fields[eWeferences]);
3868                 msg->cm_fields[eWeferences] = strdup(references);
3869         }
3870
3871         if (preformatted_text != NULL) {
3872                 msg->cm_fields[eMesageText] = preformatted_text;
3873         }
3874         else {
3875                 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3876         }
3877
3878         return(msg);
3879 }
3880
3881
3882
3883 /*
3884  * message entry  -  mode 0 (normal)
3885  */
3886 void cmd_ent0(char *entargs)
3887 {
3888         struct CitContext *CCC = CC;
3889         int post = 0;
3890         char recp[SIZ];
3891         char cc[SIZ];
3892         char bcc[SIZ];
3893         char supplied_euid[128];
3894         int anon_flag = 0;
3895         int format_type = 0;
3896         char newusername[256];
3897         char newuseremail[256];
3898         struct CtdlMessage *msg;
3899         int anonymous = 0;
3900         char errmsg[SIZ];
3901         int err = 0;
3902         struct recptypes *valid = NULL;
3903         struct recptypes *valid_to = NULL;
3904         struct recptypes *valid_cc = NULL;
3905         struct recptypes *valid_bcc = NULL;
3906         char subject[SIZ];
3907         int subject_required = 0;
3908         int do_confirm = 0;
3909         long msgnum;
3910         int i, j;
3911         char buf[256];
3912         int newuseremail_ok = 0;
3913         char references[SIZ];
3914         char *ptr;
3915
3916         unbuffer_output();
3917
3918         post = extract_int(entargs, 0);
3919         extract_token(recp, entargs, 1, '|', sizeof recp);
3920         anon_flag = extract_int(entargs, 2);
3921         format_type = extract_int(entargs, 3);
3922         extract_token(subject, entargs, 4, '|', sizeof subject);
3923         extract_token(newusername, entargs, 5, '|', sizeof newusername);
3924         do_confirm = extract_int(entargs, 6);
3925         extract_token(cc, entargs, 7, '|', sizeof cc);
3926         extract_token(bcc, entargs, 8, '|', sizeof bcc);
3927         switch(CC->room.QRdefaultview) {
3928         case VIEW_NOTES:
3929         case VIEW_WIKI:
3930                 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3931                 break;
3932         default:
3933                 supplied_euid[0] = 0;
3934                 break;
3935         }
3936         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3937         extract_token(references, entargs, 11, '|', sizeof references);
3938         for (ptr=references; *ptr != 0; ++ptr) {
3939                 if (*ptr == '!') *ptr = '|';
3940         }
3941
3942         /* first check to make sure the request is valid. */
3943
3944         err = CtdlDoIHavePermissionToPostInThisRoom(
3945                 errmsg,
3946                 sizeof errmsg,
3947                 NULL,
3948                 POST_LOGGED_IN,
3949                 (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
3950                 );
3951         if (err)
3952         {
3953                 cprintf("%d %s\n", err, errmsg);
3954                 return;
3955         }
3956
3957         /* Check some other permission type things. */
3958
3959         if (IsEmptyStr(newusername)) {
3960                 strcpy(newusername, CCC->user.fullname);
3961         }
3962         if (  (CCC->user.axlevel < AxAideU)
3963               && (strcasecmp(newusername, CCC->user.fullname))
3964               && (strcasecmp(newusername, CCC->cs_inet_fn))
3965                 ) {     
3966                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3967                         ERROR + HIGHER_ACCESS_REQUIRED,
3968                         newusername
3969                         );
3970                 return;
3971         }
3972
3973
3974         if (IsEmptyStr(newuseremail)) {
3975                 newuseremail_ok = 1;
3976         }
3977
3978         if (!IsEmptyStr(newuseremail)) {
3979                 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
3980                         newuseremail_ok = 1;
3981                 }
3982                 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
3983                         j = num_tokens(CCC->cs_inet_other_emails, '|');
3984                         for (i=0; i<j; ++i) {
3985                                 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
3986                                 if (!strcasecmp(newuseremail, buf)) {
3987                                         newuseremail_ok = 1;
3988                                 }
3989                         }
3990                 }
3991         }
3992
3993         if (!newuseremail_ok) {
3994                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3995                         ERROR + HIGHER_ACCESS_REQUIRED,
3996                         newuseremail
3997                         );
3998                 return;
3999         }
4000
4001         CCC->cs_flags |= CS_POSTING;
4002
4003         /* In mailbox rooms we have to behave a little differently --
4004          * make sure the user has specified at least one recipient.  Then
4005          * validate the recipient(s).  We do this for the Mail> room, as
4006          * well as any room which has the "Mailbox" view set - unless it
4007          * is the DRAFTS room which does not require recipients
4008          */
4009
4010         if ( (  ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4011                 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4012                      ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4013                 if (CCC->user.axlevel < AxProbU) {
4014                         strcpy(recp, "sysop");
4015                         strcpy(cc, "");
4016                         strcpy(bcc, "");
4017                 }
4018
4019                 valid_to = validate_recipients(recp, NULL, 0);
4020                 if (valid_to->num_error > 0) {
4021                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4022                         free_recipients(valid_to);
4023                         return;
4024                 }
4025
4026                 valid_cc = validate_recipients(cc, NULL, 0);
4027                 if (valid_cc->num_error > 0) {
4028                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4029                         free_recipients(valid_to);
4030                         free_recipients(valid_cc);
4031                         return;
4032                 }
4033
4034                 valid_bcc = validate_recipients(bcc, NULL, 0);
4035                 if (valid_bcc->num_error > 0) {
4036                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4037                         free_recipients(valid_to);
4038                         free_recipients(valid_cc);
4039                         free_recipients(valid_bcc);
4040                         return;
4041                 }
4042
4043                 /* Recipient required, but none were specified */
4044                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4045                         free_recipients(valid_to);
4046                         free_recipients(valid_cc);
4047                         free_recipients(valid_bcc);
4048                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4049                         return;
4050                 }
4051
4052                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4053                         if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4054                                 cprintf("%d You do not have permission "
4055                                         "to send Internet mail.\n",
4056                                         ERROR + HIGHER_ACCESS_REQUIRED);
4057                                 free_recipients(valid_to);
4058                                 free_recipients(valid_cc);
4059                                 free_recipients(valid_bcc);
4060                                 return;
4061                         }
4062                 }
4063
4064                 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
4065                      && (CCC->user.axlevel < AxNetU) ) {
4066                         cprintf("%d Higher access required for network mail.\n",
4067                                 ERROR + HIGHER_ACCESS_REQUIRED);
4068                         free_recipients(valid_to);
4069                         free_recipients(valid_cc);
4070                         free_recipients(valid_bcc);
4071                         return;
4072                 }
4073         
4074                 if ((RESTRICT_INTERNET == 1)
4075                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4076                     && ((CCC->user.flags & US_INTERNET) == 0)
4077                     && (!CCC->internal_pgm)) {
4078                         cprintf("%d You don't have access to Internet mail.\n",
4079                                 ERROR + HIGHER_ACCESS_REQUIRED);
4080                         free_recipients(valid_to);
4081                         free_recipients(valid_cc);
4082                         free_recipients(valid_bcc);
4083                         return;
4084                 }
4085
4086         }
4087
4088         /* Is this a room which has anonymous-only or anonymous-option? */
4089         anonymous = MES_NORMAL;
4090         if (CCC->room.QRflags & QR_ANONONLY) {
4091                 anonymous = MES_ANONONLY;
4092         }
4093         if (CCC->room.QRflags & QR_ANONOPT) {
4094                 if (anon_flag == 1) {   /* only if the user requested it */
4095                         anonymous = MES_ANONOPT;
4096                 }
4097         }
4098
4099         if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4100                 recp[0] = 0;
4101         }
4102
4103         /* Recommend to the client that the use of a message subject is
4104          * strongly recommended in this room, if either the SUBJECTREQ flag
4105          * is set, or if there is one or more Internet email recipients.
4106          */
4107         if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4108         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4109         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4110         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4111
4112         /* If we're only checking the validity of the request, return
4113          * success without creating the message.
4114          */
4115         if (post == 0) {
4116                 cprintf("%d %s|%d\n", CIT_OK,
4117                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4118                         subject_required);
4119                 free_recipients(valid_to);
4120                 free_recipients(valid_cc);
4121                 free_recipients(valid_bcc);
4122                 return;
4123         }
4124
4125         /* We don't need these anymore because we'll do it differently below */
4126         free_recipients(valid_to);
4127         free_recipients(valid_cc);
4128         free_recipients(valid_bcc);
4129
4130         /* Read in the message from the client. */
4131         if (do_confirm) {
4132                 cprintf("%d send message\n", START_CHAT_MODE);
4133         } else {
4134                 cprintf("%d send message\n", SEND_LISTING);
4135         }
4136
4137         msg = CtdlMakeMessage(&CCC->user, recp, cc,
4138                               CCC->room.QRname, anonymous, format_type,
4139                               newusername, newuseremail, subject,
4140                               ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4141                               NULL, references);
4142
4143         /* Put together one big recipients struct containing to/cc/bcc all in
4144          * one.  This is for the envelope.
4145          */
4146         char *all_recps = malloc(SIZ * 3);
4147         strcpy(all_recps, recp);
4148         if (!IsEmptyStr(cc)) {
4149                 if (!IsEmptyStr(all_recps)) {
4150                         strcat(all_recps, ",");
4151                 }
4152                 strcat(all_recps, cc);
4153         }
4154         if (!IsEmptyStr(bcc)) {
4155                 if (!IsEmptyStr(all_recps)) {
4156                         strcat(all_recps, ",");
4157                 }
4158                 strcat(all_recps, bcc);
4159         }
4160         if (!IsEmptyStr(all_recps)) {
4161                 valid = validate_recipients(all_recps, NULL, 0);
4162         }
4163         else {
4164                 valid = NULL;
4165         }
4166         free(all_recps);
4167
4168         if ((valid != NULL) && (valid->num_room == 1))
4169         {
4170                 /* posting into an ML room? set the envelope from 
4171                  * to the actual mail address so others get a valid
4172                  * reply-to-header.
4173                  */
4174                 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4175         }
4176
4177         if (msg != NULL) {
4178                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4179                 if (do_confirm) {
4180                         cprintf("%ld\n", msgnum);
4181
4182                         if (StrLength(CCC->StatusMessage) > 0) {
4183                                 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4184                         }
4185                         else if (msgnum >= 0L) {
4186                                 client_write(HKEY("Message accepted.\n"));
4187                         }
4188                         else {
4189                                 client_write(HKEY("Internal error.\n"));
4190                         }
4191
4192                         if (!CM_IsEmpty(msg, eExclusiveID)) {
4193                                 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4194                         } else {
4195                                 cprintf("\n");
4196                         }
4197                         cprintf("000\n");
4198                 }
4199
4200                 CM_Free(msg);
4201         }
4202         if (valid != NULL) {
4203                 free_recipients(valid);
4204         }
4205         return;
4206 }
4207
4208
4209
4210 /*
4211  * API function to delete messages which match a set of criteria
4212  * (returns the actual number of messages deleted)
4213  */
4214 int CtdlDeleteMessages(char *room_name,         /* which room */
4215                        long *dmsgnums,          /* array of msg numbers to be deleted */
4216                        int num_dmsgnums,        /* number of msgs to be deleted, or 0 for "any" */
4217                        char *content_type       /* or "" for any.  regular expressions expected. */
4218         )
4219 {
4220         struct CitContext *CCC = CC;
4221         struct ctdlroom qrbuf;
4222         struct cdbdata *cdbfr;
4223         long *msglist = NULL;
4224         long *dellist = NULL;
4225         int num_msgs = 0;
4226         int i, j;
4227         int num_deleted = 0;
4228         int delete_this;
4229         struct MetaData smi;
4230         regex_t re;
4231         regmatch_t pm;
4232         int need_to_free_re = 0;
4233
4234         if (content_type) if (!IsEmptyStr(content_type)) {
4235                         regcomp(&re, content_type, 0);
4236                         need_to_free_re = 1;
4237                 }
4238         MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4239                    room_name, num_dmsgnums, content_type);
4240
4241         /* get room record, obtaining a lock... */
4242         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4243                 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4244                            room_name);
4245                 if (need_to_free_re) regfree(&re);
4246                 return (0);     /* room not found */
4247         }
4248         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4249
4250         if (cdbfr != NULL) {
4251                 dellist = malloc(cdbfr->len);
4252                 msglist = (long *) cdbfr->ptr;
4253                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4254                 num_msgs = cdbfr->len / sizeof(long);
4255                 cdb_free(cdbfr);
4256         }
4257         if (num_msgs > 0) {
4258                 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4259                 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4260                 int have_more_del = 1;
4261
4262                 num_msgs = sort_msglist(msglist, num_msgs);
4263                 if (num_dmsgnums > 1)
4264                         num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4265 /*
4266                 {
4267                         StrBuf *dbg = NewStrBuf();
4268                         for (i = 0; i < num_dmsgnums; i++)
4269                                 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4270                         MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
4271                         FreeStrBuf(&dbg);
4272                 }
4273 */
4274                 i = 0; j = 0;
4275                 while ((i < num_msgs) && (have_more_del)) {
4276                         delete_this = 0x00;
4277
4278                         /* Set/clear a bit for each criterion */
4279
4280                         /* 0 messages in the list or a null list means that we are
4281                          * interested in deleting any messages which meet the other criteria.
4282                          */
4283                         if (have_delmsgs) {
4284                                 delete_this |= 0x01;
4285                         }
4286                         else {
4287                                 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4288
4289                                 if (i >= num_msgs)
4290                                         continue;
4291
4292                                 if (msglist[i] == dmsgnums[j]) {
4293                                         delete_this |= 0x01;
4294                                 }
4295                                 j++;
4296                                 have_more_del = (j < num_dmsgnums);
4297                         }
4298
4299                         if (have_contenttype) {
4300                                 GetMetaData(&smi, msglist[i]);
4301                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4302                                         delete_this |= 0x02;
4303                                 }
4304                         } else {
4305                                 delete_this |= 0x02;
4306                         }
4307
4308                         /* Delete message only if all bits are set */
4309                         if (delete_this == 0x03) {
4310                                 dellist[num_deleted++] = msglist[i];
4311                                 msglist[i] = 0L;
4312                         }
4313                         i++;
4314                 }
4315 /*
4316                 {
4317                         StrBuf *dbg = NewStrBuf();
4318                         for (i = 0; i < num_deleted; i++)
4319                                 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4320                         MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
4321                         FreeStrBuf(&dbg);
4322                 }
4323 */
4324                 num_msgs = sort_msglist(msglist, num_msgs);
4325                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4326                           msglist, (int)(num_msgs * sizeof(long)));
4327
4328                 if (num_msgs > 0)
4329                         qrbuf.QRhighest = msglist[num_msgs - 1];
4330                 else
4331                         qrbuf.QRhighest = 0;
4332         }
4333         CtdlPutRoomLock(&qrbuf);
4334
4335         /* Go through the messages we pulled out of the index, and decrement
4336          * their reference counts by 1.  If this is the only room the message
4337          * was in, the reference count will reach zero and the message will
4338          * automatically be deleted from the database.  We do this in a
4339          * separate pass because there might be plug-in hooks getting called,
4340          * and we don't want that happening during an S_ROOMS critical
4341          * section.
4342          */
4343         if (num_deleted) {
4344                 for (i=0; i<num_deleted; ++i) {
4345                         PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4346                 }
4347                 AdjRefCountList(dellist, num_deleted, -1);
4348         }
4349         /* Now free the memory we used, and go away. */
4350         if (msglist != NULL) free(msglist);
4351         if (dellist != NULL) free(dellist);
4352         MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
4353         if (need_to_free_re) regfree(&re);
4354         return (num_deleted);
4355 }
4356
4357 /*
4358  * Delete message from current room
4359  */
4360 void cmd_dele(char *args)
4361 {
4362         int num_deleted;
4363         int i;
4364         char msgset[SIZ];
4365         char msgtok[32];
4366         long *msgs;
4367         int num_msgs = 0;
4368
4369         extract_token(msgset, args, 0, '|', sizeof msgset);
4370         num_msgs = num_tokens(msgset, ',');
4371         if (num_msgs < 1) {
4372                 cprintf("%d Nothing to do.\n", CIT_OK);
4373                 return;
4374         }
4375
4376         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4377                 cprintf("%d Higher access required.\n",
4378                         ERROR + HIGHER_ACCESS_REQUIRED);
4379                 return;
4380         }
4381
4382         /*
4383          * Build our message set to be moved/copied
4384          */
4385         msgs = malloc(num_msgs * sizeof(long));
4386         for (i=0; i<num_msgs; ++i) {
4387                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4388                 msgs[i] = atol(msgtok);
4389         }
4390
4391         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4392         free(msgs);
4393
4394         if (num_deleted) {
4395                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4396                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4397         } else {
4398                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4399         }
4400 }
4401
4402
4403
4404
4405 /*
4406  * move or copy a message to another room
4407  */
4408 void cmd_move(char *args)
4409 {
4410         char msgset[SIZ];
4411         char msgtok[32];
4412         long *msgs;
4413         int num_msgs = 0;
4414
4415         char targ[ROOMNAMELEN];
4416         struct ctdlroom qtemp;
4417         int err;
4418         int is_copy = 0;
4419         int ra;
4420         int permit = 0;
4421         int i;
4422
4423         extract_token(msgset, args, 0, '|', sizeof msgset);
4424         num_msgs = num_tokens(msgset, ',');
4425         if (num_msgs < 1) {
4426                 cprintf("%d Nothing to do.\n", CIT_OK);
4427                 return;
4428         }
4429
4430         extract_token(targ, args, 1, '|', sizeof targ);
4431         convert_room_name_macros(targ, sizeof targ);
4432         targ[ROOMNAMELEN - 1] = 0;
4433         is_copy = extract_int(args, 2);
4434
4435         if (CtdlGetRoom(&qtemp, targ) != 0) {
4436                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4437                 return;
4438         }
4439
4440         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4441                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4442                 return;
4443         }
4444
4445         CtdlGetUser(&CC->user, CC->curr_user);
4446         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4447
4448         /* Check for permission to perform this operation.
4449          * Remember: "CC->room" is source, "qtemp" is target.
4450          */
4451         permit = 0;
4452
4453         /* Admins can move/copy */
4454         if (CC->user.axlevel >= AxAideU) permit = 1;
4455
4456         /* Room aides can move/copy */
4457         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4458
4459         /* Permit move/copy from personal rooms */
4460         if ((CC->room.QRflags & QR_MAILBOX)
4461             && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4462
4463         /* Permit only copy from public to personal room */
4464         if ( (is_copy)
4465              && (!(CC->room.QRflags & QR_MAILBOX))
4466              && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4467
4468         /* Permit message removal from collaborative delete rooms */
4469         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4470
4471         /* Users allowed to post into the target room may move into it too. */
4472         if ((CC->room.QRflags & QR_MAILBOX) && 
4473             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4474
4475         /* User must have access to target room */
4476         if (!(ra & UA_KNOWN))  permit = 0;
4477
4478         if (!permit) {
4479                 cprintf("%d Higher access required.\n",
4480                         ERROR + HIGHER_ACCESS_REQUIRED);
4481                 return;
4482         }
4483
4484         /*
4485          * Build our message set to be moved/copied
4486          */
4487         msgs = malloc(num_msgs * sizeof(long));
4488         for (i=0; i<num_msgs; ++i) {
4489                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4490                 msgs[i] = atol(msgtok);
4491         }
4492
4493         /*
4494          * Do the copy
4495          */
4496         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4497         if (err != 0) {
4498                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4499                         err, targ, err);
4500                 free(msgs);
4501                 return;
4502         }
4503
4504         /* Now delete the message from the source room,
4505          * if this is a 'move' rather than a 'copy' operation.
4506          */
4507         if (is_copy == 0) {
4508                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4509         }
4510         free(msgs);
4511
4512         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4513 }
4514
4515
4516
4517 /*
4518  * GetMetaData()  -  Get the supplementary record for a message
4519  */
4520 void GetMetaData(struct MetaData *smibuf, long msgnum)
4521 {
4522
4523         struct cdbdata *cdbsmi;
4524         long TheIndex;
4525
4526         memset(smibuf, 0, sizeof(struct MetaData));
4527         smibuf->meta_msgnum = msgnum;
4528         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4529
4530         /* Use the negative of the message number for its supp record index */
4531         TheIndex = (0L - msgnum);
4532
4533         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4534         if (cdbsmi == NULL) {
4535                 return;         /* record not found; go with defaults */
4536         }
4537         memcpy(smibuf, cdbsmi->ptr,
4538                ((cdbsmi->len > sizeof(struct MetaData)) ?
4539                 sizeof(struct MetaData) : cdbsmi->len));
4540         cdb_free(cdbsmi);
4541         return;
4542 }
4543
4544
4545 /*
4546  * PutMetaData()  -  (re)write supplementary record for a message
4547  */
4548 void PutMetaData(struct MetaData *smibuf)
4549 {
4550         long TheIndex;
4551
4552         /* Use the negative of the message number for the metadata db index */
4553         TheIndex = (0L - smibuf->meta_msgnum);
4554
4555         cdb_store(CDB_MSGMAIN,
4556                   &TheIndex, (int)sizeof(long),
4557                   smibuf, (int)sizeof(struct MetaData));
4558
4559 }
4560
4561 /*
4562  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4563  *                 (These are just queued -- we actually process them later.)
4564  */
4565 void AdjRefCount(long msgnum, int incr)
4566 {
4567         struct CitContext *CCC = CC;
4568         struct arcq new_arcq;
4569         int rv = 0;
4570
4571         MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
4572
4573         begin_critical_section(S_SUPPMSGMAIN);
4574         if (arcfp == NULL) {
4575                 arcfp = fopen(file_arcq, "ab+");
4576                 chown(file_arcq, CTDLUID, (-1));
4577                 chmod(file_arcq, 0600);
4578         }
4579         end_critical_section(S_SUPPMSGMAIN);
4580
4581         /* msgnum < 0 means that we're trying to close the file */
4582         if (msgnum < 0) {
4583                 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4584                 begin_critical_section(S_SUPPMSGMAIN);
4585                 if (arcfp != NULL) {
4586                         fclose(arcfp);
4587                         arcfp = NULL;
4588                 }
4589                 end_critical_section(S_SUPPMSGMAIN);
4590                 return;
4591         }
4592
4593         /*
4594          * If we can't open the queue, perform the operation synchronously.
4595          */
4596         if (arcfp == NULL) {
4597                 TDAP_AdjRefCount(msgnum, incr);
4598                 return;
4599         }
4600
4601         new_arcq.arcq_msgnum = msgnum;
4602         new_arcq.arcq_delta = incr;
4603         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4604         if (rv == -1) {
4605                 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
4606                            file_arcq,
4607                            strerror(errno));
4608         }
4609         fflush(arcfp);
4610
4611         return;
4612 }
4613
4614 void AdjRefCountList(long *msgnum, long nmsg, int incr)
4615 {
4616         struct CitContext *CCC = CC;
4617         long i, the_size, offset;
4618         struct arcq *new_arcq;
4619         int rv = 0;
4620
4621         MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
4622
4623         begin_critical_section(S_SUPPMSGMAIN);
4624         if (arcfp == NULL) {
4625                 arcfp = fopen(file_arcq, "ab+");
4626                 chown(file_arcq, CTDLUID, (-1));
4627                 chmod(file_arcq, 0600);
4628         }
4629         end_critical_section(S_SUPPMSGMAIN);
4630
4631         /*
4632          * If we can't open the queue, perform the operation synchronously.
4633          */
4634         if (arcfp == NULL) {
4635                 for (i = 0; i < nmsg; i++)
4636                         TDAP_AdjRefCount(msgnum[i], incr);
4637                 return;
4638         }
4639
4640         the_size = sizeof(struct arcq) * nmsg;
4641         new_arcq = malloc(the_size);
4642         for (i = 0; i < nmsg; i++) {
4643                 new_arcq[i].arcq_msgnum = msgnum[i];
4644                 new_arcq[i].arcq_delta = incr;
4645         }
4646         rv = 0;
4647         offset = 0;
4648         while ((rv >= 0) && (offset < the_size))
4649         {
4650                 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
4651                 if (rv == -1) {
4652                         MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
4653                                    file_arcq,
4654                                    strerror(errno));
4655                 }
4656                 else {
4657                         offset += rv;
4658                 }
4659         }
4660         free(new_arcq);
4661         fflush(arcfp);
4662
4663         return;
4664 }
4665
4666
4667 /*
4668  * TDAP_ProcessAdjRefCountQueue()
4669  *
4670  * Process the queue of message count adjustments that was created by calls
4671  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4672  * for each one.  This should be an "off hours" operation.
4673  */
4674 int TDAP_ProcessAdjRefCountQueue(void)
4675 {
4676         struct CitContext *CCC = CC;
4677         char file_arcq_temp[PATH_MAX];
4678         int r;
4679         FILE *fp;
4680         struct arcq arcq_rec;
4681         int num_records_processed = 0;
4682
4683         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4684
4685         begin_critical_section(S_SUPPMSGMAIN);
4686         if (arcfp != NULL) {
4687                 fclose(arcfp);
4688                 arcfp = NULL;
4689         }
4690
4691         r = link(file_arcq, file_arcq_temp);
4692         if (r != 0) {
4693                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4694                 end_critical_section(S_SUPPMSGMAIN);
4695                 return(num_records_processed);
4696         }
4697
4698         unlink(file_arcq);
4699         end_critical_section(S_SUPPMSGMAIN);
4700
4701         fp = fopen(file_arcq_temp, "rb");
4702         if (fp == NULL) {
4703                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4704                 return(num_records_processed);
4705         }
4706
4707         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4708                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4709                 ++num_records_processed;
4710         }
4711
4712         fclose(fp);
4713         r = unlink(file_arcq_temp);
4714         if (r != 0) {
4715                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4716         }
4717
4718         return(num_records_processed);
4719 }
4720
4721
4722
4723 /*
4724  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4725  *                      This one does it "for real" because it's called by
4726  *                      the autopurger function that processes the queue
4727  *                      created by AdjRefCount().   If a message's reference
4728  *                      count becomes zero, we also delete the message from
4729  *                      disk and de-index it.
4730  */
4731 void TDAP_AdjRefCount(long msgnum, int incr)
4732 {
4733         struct CitContext *CCC = CC;
4734
4735         struct MetaData smi;
4736         long delnum;
4737
4738         /* This is a *tight* critical section; please keep it that way, as
4739          * it may get called while nested in other critical sections.  
4740          * Complicating this any further will surely cause deadlock!
4741          */
4742         begin_critical_section(S_SUPPMSGMAIN);
4743         GetMetaData(&smi, msgnum);
4744         smi.meta_refcount += incr;
4745         PutMetaData(&smi);
4746         end_critical_section(S_SUPPMSGMAIN);
4747         MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4748                    msgnum, incr, smi.meta_refcount
4749                 );
4750
4751         /* If the reference count is now zero, delete the message
4752          * (and its supplementary record as well).
4753          */
4754         if (smi.meta_refcount == 0) {
4755                 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
4756                 
4757                 /* Call delete hooks with NULL room to show it has gone altogether */
4758                 PerformDeleteHooks(NULL, msgnum);
4759
4760                 /* Remove from message base */
4761                 delnum = msgnum;
4762                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4763                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4764
4765                 /* Remove metadata record */
4766                 delnum = (0L - msgnum);
4767                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4768         }
4769
4770 }
4771
4772 /*
4773  * Write a generic object to this room
4774  *
4775  * Note: this could be much more efficient.  Right now we use two temporary
4776  * files, and still pull the message into memory as with all others.
4777  */
4778 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4779                      char *content_type,                /* MIME type of this object */
4780                      char *raw_message,         /* Data to be written */
4781                      off_t raw_length,          /* Size of raw_message */
4782                      struct ctdluser *is_mailbox,       /* Mailbox room? */
4783                      int is_binary,                     /* Is encoding necessary? */
4784                      int is_unique,                     /* Del others of this type? */
4785                      unsigned int flags         /* Internal save flags */
4786         )
4787 {
4788         struct CitContext *CCC = CC;
4789         struct ctdlroom qrbuf;
4790         char roomname[ROOMNAMELEN];
4791         struct CtdlMessage *msg;
4792         char *encoded_message = NULL;
4793
4794         if (is_mailbox != NULL) {
4795                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4796         }
4797         else {
4798                 safestrncpy(roomname, req_room, sizeof(roomname));
4799         }
4800
4801         MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
4802
4803         if (is_binary) {
4804                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4805         }
4806         else {
4807                 encoded_message = malloc((size_t)(raw_length + 4096));
4808         }
4809
4810         sprintf(encoded_message, "Content-type: %s\n", content_type);
4811
4812         if (is_binary) {
4813                 sprintf(&encoded_message[strlen(encoded_message)],
4814                         "Content-transfer-encoding: base64\n\n"
4815                         );
4816         }
4817         else {
4818                 sprintf(&encoded_message[strlen(encoded_message)],
4819                         "Content-transfer-encoding: 7bit\n\n"
4820                         );
4821         }
4822
4823         if (is_binary) {
4824                 CtdlEncodeBase64(
4825                         &encoded_message[strlen(encoded_message)],
4826                         raw_message,
4827                         (int)raw_length,
4828                         0
4829                         );
4830         }
4831         else {
4832                 memcpy(
4833                         &encoded_message[strlen(encoded_message)],
4834                         raw_message,
4835                         (int)(raw_length+1)
4836                         );
4837         }
4838
4839         MSGM_syslog(LOG_DEBUG, "Allocating\n");
4840         msg = malloc(sizeof(struct CtdlMessage));
4841         memset(msg, 0, sizeof(struct CtdlMessage));
4842         msg->cm_magic = CTDLMESSAGE_MAGIC;
4843         msg->cm_anon_type = MES_NORMAL;
4844         msg->cm_format_type = 4;
4845         msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
4846         msg->cm_fields[eOriginalRoom] = strdup(req_room);
4847         msg->cm_fields[eNodeName] = strdup(config.c_nodename);
4848         msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
4849         msg->cm_flags = flags;
4850         
4851         msg->cm_fields[eMesageText] = encoded_message;
4852
4853         /* Create the requested room if we have to. */
4854         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4855                 CtdlCreateRoom(roomname, 
4856                                ( (is_mailbox != NULL) ? 5 : 3 ),
4857                                "", 0, 1, 0, VIEW_BBS);
4858         }
4859         /* If the caller specified this object as unique, delete all
4860          * other objects of this type that are currently in the room.
4861          */
4862         if (is_unique) {
4863                 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
4864                            CtdlDeleteMessages(roomname, NULL, 0, content_type)
4865                         );
4866         }
4867         /* Now write the data */
4868         CtdlSubmitMsg(msg, NULL, roomname, 0);
4869         CM_Free(msg);
4870 }
4871
4872
4873
4874 /*****************************************************************************/
4875 /*                      MODULE INITIALIZATION STUFF                          */
4876 /*****************************************************************************/
4877 void SetMessageDebugEnabled(const int n)
4878 {
4879         MessageDebugEnabled = n;
4880 }
4881 CTDL_MODULE_INIT(msgbase)
4882 {
4883         if (!threading) {
4884                 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
4885
4886                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4887                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4888                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4889                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4890                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4891                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4892                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4893                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4894                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4895                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4896                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4897         }
4898
4899         /* return our Subversion id for the Log */
4900         return "msgbase";
4901 }