1 // Implements the message store.
3 // Copyright (c) 1987-2023 by the citadel.org team
5 // This program is open source software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License version 3.
14 #include <libcitadel.h>
15 #include "ctdl_module.h"
16 #include "citserver.h"
19 #include "clientsocket.h"
23 #include "internet_addressing.h"
24 #include "euidindex.h"
26 #include "journaling.h"
28 struct addresses_to_be_filed *atbf = NULL;
30 // These are the four-character field headers we use when outputting
31 // messages in Citadel format (as opposed to RFC822 format).
33 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
34 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
35 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
36 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
37 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
38 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
40 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
42 "from", // A -> eAuthor
43 NULL, // B -> eBig_message
44 NULL, // C (formerly used as eRemoteRoom)
45 NULL, // D (formerly used as eDestination)
46 "exti", // E -> eXclusivID
47 "rfca", // F -> erFc822Addr
49 "hnod", // H (formerly used as eHumanNode)
50 "msgn", // I -> emessageId
51 "jrnl", // J -> eJournal
52 "rep2", // K -> eReplyTo
53 "list", // L -> eListID
54 "text", // M -> eMesageText
55 NULL, // N (formerly used as eNodename)
56 "room", // O -> eOriginalRoom
57 "path", // P -> eMessagePath
59 "rcpt", // R -> eRecipient
60 NULL, // S (formerly used as eSpecialField)
61 "time", // T -> eTimestamp
62 "subj", // U -> eMsgSubject
63 "nvto", // V -> eenVelopeTo
64 "wefw", // W -> eWeferences
66 "cccc", // Y -> eCarbonCopY
71 HashList *msgKeyLookup = NULL;
73 int GetFieldFromMnemonic(eMsgField *f, const char* c) {
75 if (GetHash(msgKeyLookup, c, 4, &v)) {
82 void FillMsgKeyLookupTable(void) {
85 msgKeyLookup = NewHash (1, FourHash);
87 for (i=0; i < 91; i++) {
88 if (msgkeys[i] != NULL) {
89 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
95 eMsgField FieldOrder[] = {
96 /* Important fields */
104 /* Semi-important fields */
109 /* G is not used yet */
112 /* Q is not used yet */
114 /* X is not used yet */
115 /* Z is not used yet */
122 /* Message text (MUST be last) */
124 /* Not saved to disk:
129 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
132 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
133 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
137 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
138 if (Msg->cm_fields[which] != NULL) {
139 free (Msg->cm_fields[which]);
141 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
142 length = strlen(buf);
144 Msg->cm_fields[which] = malloc(length + 1);
145 memcpy(Msg->cm_fields[which], buf, length);
146 Msg->cm_fields[which][length] = '\0';
147 Msg->cm_lengths[which] = length;
151 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
154 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
155 CM_SetField(Msg, which, buf, len);
159 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
160 if (Msg->cm_fields[WhichToCut] == NULL)
163 if (Msg->cm_lengths[WhichToCut] > maxlen)
165 Msg->cm_fields[WhichToCut][maxlen] = '\0';
166 Msg->cm_lengths[WhichToCut] = maxlen;
171 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
172 if (Msg->cm_fields[which] != NULL)
173 free (Msg->cm_fields[which]);
174 Msg->cm_fields[which] = NULL;
175 Msg->cm_lengths[which] = 0;
179 void CM_Flush(struct CtdlMessage *Msg) {
182 if (CM_IsValidMsg(Msg) == 0) {
186 for (i = 0; i < 256; ++i) {
187 CM_FlushField(Msg, i);
192 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
194 if (Msg->cm_fields[WhichToPutTo] != NULL) {
195 free (Msg->cm_fields[WhichToPutTo]);
198 if (Msg->cm_fields[WhichtToCopy] != NULL) {
199 len = Msg->cm_lengths[WhichtToCopy];
200 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
201 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
202 Msg->cm_fields[WhichToPutTo][len] = '\0';
203 Msg->cm_lengths[WhichToPutTo] = len;
206 Msg->cm_fields[WhichToPutTo] = NULL;
207 Msg->cm_lengths[WhichToPutTo] = 0;
212 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
213 if (Msg->cm_fields[which] != NULL) {
218 oldmsgsize = Msg->cm_lengths[which] + 1;
219 newmsgsize = length + oldmsgsize;
221 new = malloc(newmsgsize);
222 memcpy(new, buf, length);
223 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
224 free(Msg->cm_fields[which]);
225 Msg->cm_fields[which] = new;
226 Msg->cm_lengths[which] = newmsgsize - 1;
229 Msg->cm_fields[which] = malloc(length + 1);
230 memcpy(Msg->cm_fields[which], buf, length);
231 Msg->cm_fields[which][length] = '\0';
232 Msg->cm_lengths[which] = length;
237 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
238 if (Msg->cm_fields[which] != NULL) {
239 free (Msg->cm_fields[which]);
242 Msg->cm_fields[which] = *buf;
244 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
245 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
248 Msg->cm_lengths[which] = length;
253 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
254 if (Msg->cm_fields[which] != NULL) {
255 free (Msg->cm_fields[which]);
258 Msg->cm_lengths[which] = StrLength(*buf);
259 Msg->cm_fields[which] = SmashStrBuf(buf);
263 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
264 if (Msg->cm_fields[which] != NULL) {
265 *retlen = Msg->cm_lengths[which];
266 *ret = Msg->cm_fields[which];
267 Msg->cm_fields[which] = NULL;
268 Msg->cm_lengths[which] = 0;
277 // Returns 1 if the supplied pointer points to a valid Citadel message.
278 // If the pointer is NULL or the magic number check fails, returns 0.
279 int CM_IsValidMsg(struct CtdlMessage *msg) {
283 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
284 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
291 void CM_FreeContents(struct CtdlMessage *msg) {
294 for (i = 0; i < 256; ++i)
295 if (msg->cm_fields[i] != NULL) {
296 free(msg->cm_fields[i]);
297 msg->cm_lengths[i] = 0;
300 msg->cm_magic = 0; // just in case
304 // 'Destructor' for struct CtdlMessage
305 void CM_Free(struct CtdlMessage *msg) {
306 if (CM_IsValidMsg(msg) == 0) {
307 if (msg != NULL) free (msg);
310 CM_FreeContents(msg);
315 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
317 len = OrgMsg->cm_lengths[i];
318 NewMsg->cm_fields[i] = malloc(len + 1);
319 if (NewMsg->cm_fields[i] == NULL) {
322 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
323 NewMsg->cm_fields[i][len] = '\0';
324 NewMsg->cm_lengths[i] = len;
329 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
331 struct CtdlMessage *NewMsg;
333 if (CM_IsValidMsg(OrgMsg) == 0) {
336 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
337 if (NewMsg == NULL) {
341 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
343 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
345 for (i = 0; i < 256; ++i) {
346 if (OrgMsg->cm_fields[i] != NULL) {
347 if (!CM_DupField(i, OrgMsg, NewMsg)) {
358 // Determine if a given message matches the fields in a message template.
359 // Return 0 for a successful match.
360 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
363 // If there aren't any fields in the template, all messages will match.
364 if (template == NULL) return(0);
366 // Null messages are bogus.
367 if (msg == NULL) return(1);
369 for (i='A'; i<='Z'; ++i) {
370 if (template->cm_fields[i] != NULL) {
371 if (msg->cm_fields[i] == NULL) {
372 // Considered equal if temmplate is empty string
373 if (IsEmptyStr(template->cm_fields[i])) continue;
376 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
377 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
382 // All compares succeeded: we have a match!
387 // Retrieve the "seen" message list for the current room.
388 void CtdlGetSeen(char *buf, int which_set) {
391 // Learn about the user and room in question
392 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
394 if (which_set == ctdlsetseen_seen) {
395 safestrncpy(buf, vbuf.v_seen, SIZ);
397 if (which_set == ctdlsetseen_answered) {
398 safestrncpy(buf, vbuf.v_answered, SIZ);
403 // Manipulate the "seen msgs" string (or other message set strings)
404 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
405 int target_setting, int which_set,
406 struct ctdluser *which_user, struct ctdlroom *which_room) {
420 char *is_set; // actually an array of booleans
422 // Don't bother doing *anything* if we were passed a list of zero messages
423 if (num_target_msgnums < 1) {
427 // If no room was specified, we go with the current room.
429 which_room = &CC->room;
432 // If no user was specified, we go with the current user.
434 which_user = &CC->user;
437 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
438 num_target_msgnums, target_msgnums[0],
439 (target_setting ? "SET" : "CLEAR"),
443 // Learn about the user and room in question
444 CtdlGetRelationship(&vbuf, which_user, which_room);
446 // Load the message list
447 num_msgs = CtdlFetchMsgList(which_room->QRnumber, &msglist);
453 is_set = malloc(num_msgs * sizeof(char));
454 memset(is_set, 0, (num_msgs * sizeof(char)) );
456 // Decide which message set we're manipulating
458 case ctdlsetseen_seen:
459 vset = NewStrBufPlain(vbuf.v_seen, -1);
461 case ctdlsetseen_answered:
462 vset = NewStrBufPlain(vbuf.v_answered, -1);
468 // Translate the existing sequence set into an array of booleans
469 setstr = NewStrBuf();
473 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
475 StrBufExtract_token(lostr, setstr, 0, ':');
476 if (StrBufNum_tokens(setstr, ':') >= 2) {
477 StrBufExtract_token(histr, setstr, 1, ':');
481 StrBufAppendBuf(histr, lostr, 0);
484 if (!strcmp(ChrPtr(histr), "*")) {
491 for (i = 0; i < num_msgs; ++i) {
492 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
501 // Now translate the array of booleans back into a sequence set
507 for (i=0; i<num_msgs; ++i) {
511 for (k=0; k<num_target_msgnums; ++k) {
512 if (msglist[i] == target_msgnums[k]) {
513 is_seen = target_setting;
517 if ((was_seen == 0) && (is_seen == 1)) {
520 else if ((was_seen == 1) && (is_seen == 0)) {
523 if (StrLength(vset) > 0) {
524 StrBufAppendBufPlain(vset, HKEY(","), 0);
527 StrBufAppendPrintf(vset, "%ld", hi);
530 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
534 if ((is_seen) && (i == num_msgs - 1)) {
535 if (StrLength(vset) > 0) {
536 StrBufAppendBufPlain(vset, HKEY(","), 0);
538 if ((i==0) || (was_seen == 0)) {
539 StrBufAppendPrintf(vset, "%ld", msglist[i]);
542 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
549 // We will have to stuff this string back into a 4096 byte buffer, so if it's
550 // larger than that now, truncate it by removing tokens from the beginning.
551 // The limit of 100 iterations is there to prevent an infinite loop in case
552 // something unexpected happens.
553 int number_of_truncations = 0;
554 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
555 StrBufRemove_token(vset, 0, ',');
556 ++number_of_truncations;
559 // If we're truncating the sequence set of messages marked with the 'seen' flag,
560 // we want the earliest messages (the truncated ones) to be marked, not unmarked.
561 // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
562 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
564 first_tok = NewStrBuf();
565 StrBufExtract_token(first_tok, vset, 0, ',');
566 StrBufRemove_token(vset, 0, ',');
568 if (StrBufNum_tokens(first_tok, ':') > 1) {
569 StrBufRemove_token(first_tok, 0, ':');
573 new_set = NewStrBuf();
574 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
575 StrBufAppendBuf(new_set, first_tok, 0);
576 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
577 StrBufAppendBuf(new_set, vset, 0);
580 FreeStrBuf(&first_tok);
584 // Decide which message set we're manipulating. Zero the buffers so they compress well.
586 case ctdlsetseen_seen:
587 memset(vbuf.v_seen, 0, sizeof vbuf.v_seen);
588 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
590 case ctdlsetseen_answered:
591 memset(vbuf.v_answered, 0, sizeof vbuf.v_seen);
592 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
598 CtdlSetRelationship(&vbuf, which_user, which_room);
603 // API function to perform an operation for each qualifying message in the
604 // current room. (Returns the number of messages processed.)
605 int CtdlForEachMessage(int mode, long ref, char *search_string,
607 struct CtdlMessage *compare,
608 ForEachMsgCallback CallBack,
613 long *msglist = NULL;
615 int num_processed = 0;
618 struct CtdlMessage *msg = NULL;
621 int printed_lastold = 0;
622 int num_search_msgs = 0;
623 long *search_msgs = NULL;
625 int need_to_free_re = 0;
628 if ((content_type) && (!IsEmptyStr(content_type))) {
629 regcomp(&re, content_type, 0);
633 // Learn about the user and room in question
634 if (server_shutting_down) {
635 if (need_to_free_re) regfree(&re);
638 CtdlGetUser(&CC->user, CC->curr_user);
640 if (server_shutting_down) {
641 if (need_to_free_re) regfree(&re);
644 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
646 if (server_shutting_down) {
647 if (need_to_free_re) regfree(&re);
651 // Load the message list
652 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
655 if (need_to_free_re) regfree(&re);
656 return 0; // No messages at all? No further action.
659 // Now begin the traversal.
660 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
662 // If the caller is looking for a specific MIME type, filter
663 // out all messages which are not of the type requested.
664 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
666 // This call to GetMetaData() sits inside this loop
667 // so that we only do the extra database read per msg
668 // if we need to. Doing the extra read all the time
669 // really kills the server. If we ever need to use
670 // metadata for another search criterion, we need to
671 // move the read somewhere else -- but still be smart
672 // enough to only do the read if the caller has
673 // specified something that will need it.
674 if (server_shutting_down) {
675 if (need_to_free_re) regfree(&re);
679 GetMetaData(&smi, msglist[a]);
681 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
687 num_msgs = sort_msglist(msglist, num_msgs);
689 // If a template was supplied, filter out the messages which don't match. (This could induce some delays!)
691 if (compare != NULL) {
692 for (a = 0; a < num_msgs; ++a) {
693 if (server_shutting_down) {
694 if (need_to_free_re) regfree(&re);
698 msg = CtdlFetchMessage(msglist[a], 1);
700 if (CtdlMsgCmp(msg, compare)) {
709 /* If a search string was specified, get a message list from
710 * the full text index and remove messages which aren't on both
714 * Since the lists are sorted and strictly ascending, and the
715 * output list is guaranteed to be shorter than or equal to the
716 * input list, we overwrite the bottom of the input list. This
717 * eliminates the need to memmove big chunks of the list over and
720 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
722 /* Call search module via hook mechanism.
723 * NULL means use any search function available.
724 * otherwise replace with a char * to name of search routine
726 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
728 if (num_search_msgs > 0) {
732 orig_num_msgs = num_msgs;
734 for (i=0; i<orig_num_msgs; ++i) {
735 for (j=0; j<num_search_msgs; ++j) {
736 if (msglist[i] == search_msgs[j]) {
737 msglist[num_msgs++] = msglist[i];
743 num_msgs = 0; /* No messages qualify */
745 if (search_msgs != NULL) free(search_msgs);
747 /* Now that we've purged messages which don't contain the search
748 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
755 * Now iterate through the message list, according to the
756 * criteria supplied by the caller.
759 for (a = 0; a < num_msgs; ++a) {
760 if (server_shutting_down) {
761 if (need_to_free_re) regfree(&re);
763 return num_processed;
765 thismsg = msglist[a];
766 if (mode == MSGS_ALL) {
770 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
771 if (is_seen) lastold = thismsg;
777 || ((mode == MSGS_OLD) && (is_seen))
778 || ((mode == MSGS_NEW) && (!is_seen))
779 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
780 || ((mode == MSGS_FIRST) && (a < ref))
781 || ((mode == MSGS_GT) && (thismsg > ref))
782 || ((mode == MSGS_LT) && (thismsg < ref))
783 || ((mode == MSGS_EQ) && (thismsg == ref))
786 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
788 CallBack(lastold, userdata);
794 CallBack(thismsg, userdata);
799 if (need_to_free_re) regfree(&re);
802 * We cache the most recent msglist in order to do security checks later
804 if (CC->client_socket > 0) {
805 if (CC->cached_msglist != NULL) {
806 free(CC->cached_msglist);
808 CC->cached_msglist = msglist;
809 CC->cached_num_msgs = num_msgs;
815 return num_processed;
820 * memfmout() - Citadel text formatter and paginator.
821 * Although the original purpose of this routine was to format
822 * text to the reader's screen width, all we're really using it
823 * for here is to format text out to 80 columns before sending it
824 * to the client. The client software may reformat it again.
827 char *mptr, /* where are we going to get our text from? */
828 const char *nl /* string to terminate lines with */
831 unsigned char ch = 0;
838 while (ch=*(mptr++), ch != 0) {
841 if (client_write(outbuf, len) == -1) {
842 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
846 if (client_write(nl, nllen) == -1) {
847 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
852 else if (ch == '\r') {
853 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
855 else if (isspace(ch)) {
856 if (column > 72) { /* Beyond 72 columns, break on the next space */
857 if (client_write(outbuf, len) == -1) {
858 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
862 if (client_write(nl, nllen) == -1) {
863 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
876 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
877 if (client_write(outbuf, len) == -1) {
878 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
882 if (client_write(nl, nllen) == -1) {
883 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
891 if (client_write(outbuf, len) == -1) {
892 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
895 client_write(nl, nllen);
902 * Callback function for mime parser that simply lists the part
904 void list_this_part(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
906 char *cbid, void *cbuserdata)
910 ma = (struct ma_info *)cbuserdata;
911 if (ma->is_ma == 0) {
912 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
926 * Callback function for multipart prefix
928 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
929 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
930 char *cbid, void *cbuserdata)
934 ma = (struct ma_info *)cbuserdata;
935 if (!strcasecmp(cbtype, "multipart/alternative")) {
939 if (ma->is_ma == 0) {
940 cprintf("pref=%s|%s\n", partnum, cbtype);
946 * Callback function for multipart sufffix
948 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
949 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
950 char *cbid, void *cbuserdata)
954 ma = (struct ma_info *)cbuserdata;
955 if (ma->is_ma == 0) {
956 cprintf("suff=%s|%s\n", partnum, cbtype);
958 if (!strcasecmp(cbtype, "multipart/alternative")) {
965 * Callback function for mime parser that opens a section for downloading
966 * we use serv_files function here:
968 extern void OpenCmdResult(char *filename, const char *mime_type);
969 void mime_download(char *name, char *filename, char *partnum, char *disp,
970 void *content, char *cbtype, char *cbcharset, size_t length,
971 char *encoding, char *cbid, void *cbuserdata)
975 /* Silently go away if there's already a download open. */
976 if (CC->download_fp != NULL)
980 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
981 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
983 CC->download_fp = tmpfile();
984 if (CC->download_fp == NULL) {
985 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
986 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
990 rv = fwrite(content, length, 1, CC->download_fp);
992 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
993 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
994 fclose(CC->download_fp);
995 CC->download_fp = NULL;
998 fflush(CC->download_fp);
999 rewind(CC->download_fp);
1001 OpenCmdResult(filename, cbtype);
1007 * Callback function for mime parser that outputs a section all at once.
1008 * We can specify the desired section by part number *or* content-id.
1010 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1011 void *content, char *cbtype, char *cbcharset, size_t length,
1012 char *encoding, char *cbid, void *cbuserdata)
1014 int *found_it = (int *)cbuserdata;
1017 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1018 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1021 cprintf("%d %d|-1|%s|%s|%s\n",
1028 client_write(content, length);
1033 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
1034 struct CtdlMessage *ret = NULL;
1036 const char *upper_bound;
1038 cit_uint8_t field_header;
1042 upper_bound = Buffer + Length;
1047 // Parse the three bytes that begin EVERY message on disk.
1048 // The first is always 0xFF, the on-disk magic number.
1049 // The second is the anonymous/public type byte.
1050 // The third is the format type byte (vari, fixed, or MIME).
1054 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1057 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1058 memset(ret, 0, sizeof(struct CtdlMessage));
1060 ret->cm_magic = CTDLMESSAGE_MAGIC;
1061 ret->cm_anon_type = *mptr++; // Anon type byte
1062 ret->cm_format_type = *mptr++; // Format type byte
1064 // The rest is zero or more arbitrary fields. Load them in.
1065 // We're done when we encounter either a zero-length field or
1066 // have just processed the 'M' (message text) field.
1069 field_header = '\0';
1072 while (field_header == '\0') { // work around possibly buggy messages
1073 if (mptr >= upper_bound) {
1076 field_header = *mptr++;
1078 if (mptr >= upper_bound) {
1081 which = field_header;
1084 CM_SetField(ret, which, mptr, len);
1086 mptr += len + 1; // advance to next field
1088 } while ((mptr < upper_bound) && (field_header != 'M'));
1093 // Load a message from disk into memory.
1094 // This is used by CtdlOutputMsg() and other fetch functions.
1096 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1097 // using the CM_Free(); function.
1099 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1100 struct cdbdata dmsgtext;
1101 struct CtdlMessage *ret = NULL;
1103 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1104 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1105 if (dmsgtext.ptr == NULL) {
1106 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1110 if (dmsgtext.ptr[dmsgtext.len - 1] != '\0') {
1111 // FIXME LMDB cannot write to immutable memory
1112 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1113 dmsgtext.ptr[dmsgtext.len - 1] = '\0';
1116 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext.ptr, dmsgtext.len);
1121 // Always make sure there's something in the msg text field. If
1122 // it's NULL, the message text is most likely stored separately,
1123 // so go ahead and fetch that. Failing that, just set a dummy
1124 // body so other code doesn't barf.
1126 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1127 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1128 if (dmsgtext.ptr != NULL) {
1129 CM_SetAsField(ret, eMesageText, &dmsgtext.ptr, dmsgtext.len - 1);
1132 if (CM_IsEmpty(ret, eMesageText)) {
1133 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1140 // Pre callback function for multipart/alternative
1142 // NOTE: this differs from the standard behavior for a reason. Normally when
1143 // displaying multipart/alternative you want to show the _last_ usable
1144 // format in the message. Here we show the _first_ one, because it's
1145 // usually text/plain. Since this set of functions is designed for text
1146 // output to non-MIME-aware clients, this is the desired behavior.
1148 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1149 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1150 char *cbid, void *cbuserdata)
1154 ma = (struct ma_info *)cbuserdata;
1155 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1156 if (!strcasecmp(cbtype, "multipart/alternative")) {
1160 if (!strcasecmp(cbtype, "message/rfc822")) {
1167 // Post callback function for multipart/alternative
1169 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1170 void *content, char *cbtype, char *cbcharset, size_t length,
1171 char *encoding, char *cbid, void *cbuserdata)
1175 ma = (struct ma_info *)cbuserdata;
1176 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1177 if (!strcasecmp(cbtype, "multipart/alternative")) {
1181 if (!strcasecmp(cbtype, "message/rfc822")) {
1187 // Inline callback function for mime parser that wants to display text
1188 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1189 void *content, char *cbtype, char *cbcharset, size_t length,
1190 char *encoding, char *cbid, void *cbuserdata)
1197 ma = (struct ma_info *)cbuserdata;
1200 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1201 partnum, filename, cbtype, (long)length
1204 // If we're in the middle of a multipart/alternative scope and
1205 // we've already printed another section, skip this one.
1206 if ( (ma->is_ma) && (ma->did_print) ) {
1207 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1212 if ( (!strcasecmp(cbtype, "text/plain"))
1213 || (IsEmptyStr(cbtype)) ) {
1216 client_write(wptr, length);
1217 if (wptr[length-1] != '\n') {
1224 if (!strcasecmp(cbtype, "text/html")) {
1225 ptr = html_to_ascii(content, length, 80, 0);
1227 client_write(ptr, wlen);
1228 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1235 if (ma->use_fo_hooks) {
1236 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1241 if (strncasecmp(cbtype, "multipart/", 10)) {
1242 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1243 partnum, filename, cbtype, (long)length);
1249 // The client is elegant and sophisticated and wants to be choosy about
1250 // MIME content types, so figure out which multipart/alternative part
1251 // we're going to send.
1253 // We use a system of weights. When we find a part that matches one of the
1254 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1255 // and then set ma->chosen_pref to that MIME type's position in our preference
1256 // list. If we then hit another match, we only replace the first match if
1257 // the preference value is lower.
1258 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1259 void *content, char *cbtype, char *cbcharset, size_t length,
1260 char *encoding, char *cbid, void *cbuserdata)
1266 ma = (struct ma_info *)cbuserdata;
1268 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1269 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1270 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1271 if (i < ma->chosen_pref) {
1272 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1273 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1274 ma->chosen_pref = i;
1281 // Now that we've chosen our preferred part, output it.
1282 void output_preferred(char *name,
1296 int add_newline = 0;
1299 char *decoded = NULL;
1300 size_t bytes_decoded;
1303 ma = (struct ma_info *)cbuserdata;
1305 // This is not the MIME part you're looking for...
1306 if (strcasecmp(partnum, ma->chosen_part)) return;
1308 // If the content-type of this part is in our preferred formats
1309 // list, we can simply output it verbatim.
1310 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1311 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1312 if (!strcasecmp(buf, cbtype)) {
1313 /* Yeah! Go! W00t!! */
1314 if (ma->dont_decode == 0)
1315 rc = mime_decode_now (content,
1321 break; // Give us the chance, maybe theres another one.
1323 if (rc == 0) text_content = (char *)content;
1325 text_content = decoded;
1326 length = bytes_decoded;
1329 if (text_content[length-1] != '\n') {
1332 cprintf("Content-type: %s", cbtype);
1333 if (!IsEmptyStr(cbcharset)) {
1334 cprintf("; charset=%s", cbcharset);
1336 cprintf("\nContent-length: %d\n",
1337 (int)(length + add_newline) );
1338 if (!IsEmptyStr(encoding)) {
1339 cprintf("Content-transfer-encoding: %s\n", encoding);
1342 cprintf("Content-transfer-encoding: 7bit\n");
1344 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1346 if (client_write(text_content, length) == -1)
1348 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1351 if (add_newline) cprintf("\n");
1352 if (decoded != NULL) free(decoded);
1357 // No translations required or possible: output as text/plain
1358 cprintf("Content-type: text/plain\n\n");
1360 if (ma->dont_decode == 0)
1361 rc = mime_decode_now (content,
1367 return; // Give us the chance, maybe theres another one.
1369 if (rc == 0) text_content = (char *)content;
1371 text_content = decoded;
1372 length = bytes_decoded;
1375 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1376 if (decoded != NULL) free(decoded);
1381 char desired_section[64];
1387 // Callback function
1388 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1389 void *content, char *cbtype, char *cbcharset, size_t length,
1390 char *encoding, char *cbid, void *cbuserdata)
1392 struct encapmsg *encap;
1394 encap = (struct encapmsg *)cbuserdata;
1396 // Only proceed if this is the desired section...
1397 if (!strcasecmp(encap->desired_section, partnum)) {
1398 encap->msglen = length;
1399 encap->msg = malloc(length + 2);
1400 memcpy(encap->msg, content, length);
1406 // Determine whether the specified message exists in the cached_msglist
1407 // (This is a security check)
1408 int check_cached_msglist(long msgnum) {
1410 // cases in which we skip the check
1411 if (!CC) return om_ok; // not a session
1412 if (CC->client_socket <= 0) return om_ok; // not a client session
1413 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1414 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1416 // Do a binary search within the cached_msglist for the requested msgnum
1418 int max = (CC->cached_num_msgs - 1);
1420 while (max >= min) {
1421 int middle = min + (max-min) / 2 ;
1422 if (msgnum == CC->cached_msglist[middle]) {
1425 if (msgnum > CC->cached_msglist[middle]) {
1433 return om_access_denied;
1437 // Get a message off disk. (returns om_* values found in msgbase.h)
1438 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1439 int mode, // how would you like that message?
1440 int headers_only, // eschew the message body?
1441 int do_proto, // do Citadel protocol responses?
1442 int crlf, // Use CRLF newlines instead of LF?
1443 char *section, // NULL or a message/rfc822 section
1444 int flags, // various flags; see msgbase.h
1449 struct CtdlMessage *TheMessage = NULL;
1450 int retcode = CIT_OK;
1451 struct encapmsg encap;
1454 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1456 (section ? section : "<>")
1459 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1462 if (r == om_not_logged_in) {
1463 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1466 cprintf("%d An unknown error has occurred.\n", ERROR);
1473 * Check to make sure the message is actually IN this room
1475 r = check_cached_msglist(msg_num);
1476 if (r == om_access_denied) {
1477 /* Not in the cache? We get ONE shot to check it again. */
1478 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1479 r = check_cached_msglist(msg_num);
1482 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1483 msg_num, CC->room.QRname
1486 if (r == om_access_denied) {
1487 cprintf("%d message %ld was not found in this room\n",
1488 ERROR + HIGHER_ACCESS_REQUIRED,
1497 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1498 * request that we don't even bother loading the body into memory.
1500 if (headers_only == HEADERS_FAST) {
1501 TheMessage = CtdlFetchMessage(msg_num, 0);
1504 TheMessage = CtdlFetchMessage(msg_num, 1);
1507 if (TheMessage == NULL) {
1508 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1509 ERROR + MESSAGE_NOT_FOUND, msg_num);
1510 return(om_no_such_msg);
1513 /* Here is the weird form of this command, to process only an
1514 * encapsulated message/rfc822 section.
1516 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1517 memset(&encap, 0, sizeof encap);
1518 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1519 mime_parser(CM_RANGE(TheMessage, eMesageText),
1520 *extract_encapsulated_message,
1521 NULL, NULL, (void *)&encap, 0
1524 if ((Author != NULL) && (*Author == NULL))
1527 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1529 if ((Address != NULL) && (*Address == NULL))
1532 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1534 if ((MessageID != NULL) && (*MessageID == NULL))
1537 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1539 CM_Free(TheMessage);
1543 encap.msg[encap.msglen] = 0;
1544 TheMessage = convert_internet_message(encap.msg);
1545 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1547 /* Now we let it fall through to the bottom of this
1548 * function, because TheMessage now contains the
1549 * encapsulated message instead of the top-level
1550 * message. Isn't that neat?
1555 cprintf("%d msg %ld has no part %s\n",
1556 ERROR + MESSAGE_NOT_FOUND,
1560 retcode = om_no_such_msg;
1565 /* Ok, output the message now */
1566 if (retcode == CIT_OK)
1567 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1568 if ((Author != NULL) && (*Author == NULL))
1571 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1573 if ((Address != NULL) && (*Address == NULL))
1576 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1578 if ((MessageID != NULL) && (*MessageID == NULL))
1581 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1584 CM_Free(TheMessage);
1590 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1593 char display_name[256];
1595 /* begin header processing loop for Citadel message format */
1596 safestrncpy(display_name, "<unknown>", sizeof display_name);
1597 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1598 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1599 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1600 safestrncpy(display_name, "****", sizeof display_name);
1602 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1603 safestrncpy(display_name, "anonymous", sizeof display_name);
1606 safestrncpy(display_name, buf, sizeof display_name);
1608 if ((is_room_aide())
1609 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1610 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1611 size_t tmp = strlen(display_name);
1612 snprintf(&display_name[tmp],
1613 sizeof display_name - tmp,
1618 /* Now spew the header fields in the order we like them. */
1619 for (i=0; i< NDiskFields; ++i) {
1621 Field = FieldOrder[i];
1622 if (Field != eMesageText) {
1623 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1624 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1625 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1627 if (Field == eAuthor) {
1629 cprintf("%s=%s\n", msgkeys[Field], display_name);
1632 /* Masquerade display name if needed */
1635 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1638 /* Give the client a hint about whether the message originated locally */
1639 if (Field == erFc822Addr) {
1640 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1641 cprintf("locl=yes\n"); // message originated locally.
1653 void OutputRFC822MsgHeaders(
1654 struct CtdlMessage *TheMessage,
1655 int flags, /* should the message be exported clean */
1656 const char *nl, int nlen,
1657 char *mid, long sizeof_mid,
1658 char *suser, long sizeof_suser,
1659 char *luser, long sizeof_luser,
1660 char *fuser, long sizeof_fuser,
1661 char *snode, long sizeof_snode)
1663 char datestamp[100];
1664 int subject_found = 0;
1671 for (i = 0; i < NDiskFields; ++i) {
1672 if (TheMessage->cm_fields[FieldOrder[i]]) {
1673 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1674 switch (FieldOrder[i]) {
1676 safestrncpy(luser, mptr, sizeof_luser);
1677 safestrncpy(suser, mptr, sizeof_suser);
1680 if ((flags & QP_EADDR) != 0) {
1681 mptr = qp_encode_email_addrs(mptr);
1683 sanitize_truncated_recipient(mptr);
1684 cprintf("CC: %s%s", mptr, nl);
1687 cprintf("Return-Path: %s%s", mptr, nl);
1690 cprintf("List-ID: %s%s", mptr, nl);
1693 if ((flags & QP_EADDR) != 0)
1694 mptr = qp_encode_email_addrs(mptr);
1696 while ((*hptr != '\0') && isspace(*hptr))
1698 if (!IsEmptyStr(hptr))
1699 cprintf("Envelope-To: %s%s", hptr, nl);
1702 cprintf("Subject: %s%s", mptr, nl);
1706 safestrncpy(mid, mptr, sizeof_mid);
1709 safestrncpy(fuser, mptr, sizeof_fuser);
1712 if (haschar(mptr, '@') == 0) {
1713 sanitize_truncated_recipient(mptr);
1714 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1718 if ((flags & QP_EADDR) != 0) {
1719 mptr = qp_encode_email_addrs(mptr);
1721 sanitize_truncated_recipient(mptr);
1722 cprintf("To: %s", mptr);
1727 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1728 cprintf("Date: %s%s", datestamp, nl);
1731 cprintf("References: ");
1732 k = num_tokens(mptr, '|');
1733 for (j=0; j<k; ++j) {
1734 extract_token(buf, mptr, j, '|', sizeof buf);
1735 cprintf("<%s>", buf);
1746 while ((*hptr != '\0') && isspace(*hptr))
1748 if (!IsEmptyStr(hptr))
1749 cprintf("Reply-To: %s%s", mptr, nl);
1761 /* these don't map to mime message headers. */
1764 if (mptr != mpptr) {
1769 if (subject_found == 0) {
1770 cprintf("Subject: (no subject)%s", nl);
1775 void Dump_RFC822HeadersBody(
1776 struct CtdlMessage *TheMessage,
1777 int headers_only, /* eschew the message body? */
1778 int flags, /* should the bessage be exported clean? */
1779 const char *nl, int nlen)
1781 cit_uint8_t prev_ch;
1783 const char *StartOfText = StrBufNOTNULL;
1786 int nllen = strlen(nl);
1790 mptr = TheMessage->cm_fields[eMesageText];
1793 while (*mptr != '\0') {
1794 if (*mptr == '\r') {
1801 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1803 eoh = *(mptr+1) == '\n';
1807 StartOfText = strchr(StartOfText, '\n');
1808 StartOfText = strchr(StartOfText, '\n');
1811 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1812 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1813 ((headers_only != HEADERS_NONE) &&
1814 (headers_only != HEADERS_ONLY))
1816 if (*mptr == '\n') {
1817 memcpy(&outbuf[outlen], nl, nllen);
1819 outbuf[outlen] = '\0';
1822 outbuf[outlen++] = *mptr;
1826 if (flags & ESC_DOT) {
1827 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1828 outbuf[outlen++] = '.';
1833 if (outlen > 1000) {
1834 if (client_write(outbuf, outlen) == -1) {
1835 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1838 lfSent = (outbuf[outlen - 1] == '\n');
1843 client_write(outbuf, outlen);
1844 lfSent = (outbuf[outlen - 1] == '\n');
1847 client_write(nl, nlen);
1851 /* If the format type on disk is 1 (fixed-format), then we want
1852 * everything to be output completely literally ... regardless of
1853 * what message transfer format is in use.
1855 void DumpFormatFixed(
1856 struct CtdlMessage *TheMessage,
1857 int mode, /* how would you like that message? */
1858 const char *nl, int nllen)
1866 mptr = TheMessage->cm_fields[eMesageText];
1868 if (mode == MT_MIME) {
1869 cprintf("Content-type: text/plain\n\n");
1873 while (ch = *mptr++, ch > 0) {
1877 if ((buflen > 250) && (!xlline)){
1881 while ((buflen > 0) &&
1882 (!isspace(buf[buflen])))
1888 mptr -= tbuflen - buflen;
1894 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1895 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1900 memcpy (&buf[buflen], nl, nllen);
1904 if (client_write(buf, buflen) == -1) {
1905 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1917 if (!IsEmptyStr(buf)) {
1918 cprintf("%s%s", buf, nl);
1924 * Get a message off disk. (returns om_* values found in msgbase.h)
1926 int CtdlOutputPreLoadedMsg(
1927 struct CtdlMessage *TheMessage,
1928 int mode, /* how would you like that message? */
1929 int headers_only, /* eschew the message body? */
1930 int do_proto, /* do Citadel protocol responses? */
1931 int crlf, /* Use CRLF newlines instead of LF? */
1932 int flags /* should the bessage be exported clean? */
1935 const char *nl; /* newline string */
1939 /* Buffers needed for RFC822 translation. These are all filled
1940 * using functions that are bounds-checked, and therefore we can
1941 * make them substantially smaller than SIZ.
1949 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1950 ((TheMessage == NULL) ? "NULL" : "not null"),
1951 mode, headers_only, do_proto, crlf
1954 strcpy(mid, "unknown");
1955 nl = (crlf ? "\r\n" : "\n");
1956 nlen = crlf ? 2 : 1;
1958 if (!CM_IsValidMsg(TheMessage)) {
1959 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1960 return(om_no_such_msg);
1963 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1964 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1966 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1967 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1970 /* Are we downloading a MIME component? */
1971 if (mode == MT_DOWNLOAD) {
1972 if (TheMessage->cm_format_type != FMT_RFC822) {
1974 cprintf("%d This is not a MIME message.\n",
1975 ERROR + ILLEGAL_VALUE);
1976 } else if (CC->download_fp != NULL) {
1977 if (do_proto) cprintf(
1978 "%d You already have a download open.\n",
1979 ERROR + RESOURCE_BUSY);
1981 /* Parse the message text component */
1982 mime_parser(CM_RANGE(TheMessage, eMesageText),
1983 *mime_download, NULL, NULL, NULL, 0);
1984 /* If there's no file open by this time, the requested
1985 * section wasn't found, so print an error
1987 if (CC->download_fp == NULL) {
1988 if (do_proto) cprintf(
1989 "%d Section %s not found.\n",
1990 ERROR + FILE_NOT_FOUND,
1991 CC->download_desired_section);
1994 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1997 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1998 // in a single server operation instead of opening a download file.
1999 if (mode == MT_SPEW_SECTION) {
2000 if (TheMessage->cm_format_type != FMT_RFC822) {
2002 cprintf("%d This is not a MIME message.\n",
2003 ERROR + ILLEGAL_VALUE);
2006 // Locate and parse the component specified by the caller
2008 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2010 // If section wasn't found, print an error
2013 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
2017 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2020 // now for the user-mode message reading loops
2021 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2023 // Does the caller want to skip the headers?
2024 if (headers_only == HEADERS_NONE) goto START_TEXT;
2026 // Tell the client which format type we're using.
2027 if ( (mode == MT_CITADEL) && (do_proto) ) {
2028 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2031 // nhdr=yes means that we're only displaying headers, no body
2032 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2033 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2036 cprintf("nhdr=yes\n");
2039 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2040 OutputCtdlMsgHeaders(TheMessage, do_proto);
2043 // begin header processing loop for RFC822 transfer format
2048 if (mode == MT_RFC822)
2049 OutputRFC822MsgHeaders(
2054 suser, sizeof(suser),
2055 luser, sizeof(luser),
2056 fuser, sizeof(fuser),
2057 snode, sizeof(snode)
2061 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2062 suser[i] = tolower(suser[i]);
2063 if (!isalnum(suser[i])) suser[i]='_';
2066 if (mode == MT_RFC822) {
2067 /* Construct a fun message id */
2068 cprintf("Message-ID: <%s", mid);
2069 if (strchr(mid, '@')==NULL) {
2070 cprintf("@%s", snode);
2074 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2075 cprintf("From: \"----\" <x@x.org>%s", nl);
2077 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2078 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2080 else if (!IsEmptyStr(fuser)) {
2081 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2084 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2087 /* Blank line signifying RFC822 end-of-headers */
2088 if (TheMessage->cm_format_type != FMT_RFC822) {
2093 // end header processing loop ... at this point, we're in the text
2095 if (headers_only == HEADERS_FAST) goto DONE;
2097 // Tell the client about the MIME parts in this message
2098 if (TheMessage->cm_format_type == FMT_RFC822) {
2099 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2100 memset(&ma, 0, sizeof(struct ma_info));
2101 mime_parser(CM_RANGE(TheMessage, eMesageText),
2102 (do_proto ? *list_this_part : NULL),
2103 (do_proto ? *list_this_pref : NULL),
2104 (do_proto ? *list_this_suff : NULL),
2107 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2108 Dump_RFC822HeadersBody(
2117 if (headers_only == HEADERS_ONLY) {
2121 // signify start of msg text
2122 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2123 if (do_proto) cprintf("text\n");
2126 if (TheMessage->cm_format_type == FMT_FIXED)
2129 mode, // how would you like that message?
2132 // If the message on disk is format 0 (Citadel vari-format), we
2133 // output using the formatter at 80 columns. This is the final output
2134 // form if the transfer format is RFC822, but if the transfer format
2135 // is Citadel proprietary, it'll still work, because the indentation
2136 // for new paragraphs is correct and the client will reformat the
2137 // message to the reader's screen width.
2139 if (TheMessage->cm_format_type == FMT_CITADEL) {
2140 if (mode == MT_MIME) {
2141 cprintf("Content-type: text/x-citadel-variformat\n\n");
2143 memfmout(TheMessage->cm_fields[eMesageText], nl);
2146 // If the message on disk is format 4 (MIME), we've gotta hand it
2147 // off to the MIME parser. The client has already been told that
2148 // this message is format 1 (fixed format), so the callback function
2149 // we use will display those parts as-is.
2151 if (TheMessage->cm_format_type == FMT_RFC822) {
2152 memset(&ma, 0, sizeof(struct ma_info));
2154 if (mode == MT_MIME) {
2155 ma.use_fo_hooks = 0;
2156 strcpy(ma.chosen_part, "1");
2157 ma.chosen_pref = 9999;
2158 ma.dont_decode = CC->msg4_dont_decode;
2159 mime_parser(CM_RANGE(TheMessage, eMesageText),
2160 *choose_preferred, *fixed_output_pre,
2161 *fixed_output_post, (void *)&ma, 1);
2162 mime_parser(CM_RANGE(TheMessage, eMesageText),
2163 *output_preferred, NULL, NULL, (void *)&ma, 1);
2166 ma.use_fo_hooks = 1;
2167 mime_parser(CM_RANGE(TheMessage, eMesageText),
2168 *fixed_output, *fixed_output_pre,
2169 *fixed_output_post, (void *)&ma, 0);
2174 DONE: /* now we're done */
2175 if (do_proto) cprintf("000\n");
2179 // Save one or more message pointers into a specified room
2180 // (Returns 0 for success, nonzero for failure)
2181 // roomname may be NULL to use the current room
2183 // Note that the 'supplied_msg' field may be set to NULL, in which case
2184 // the message will be fetched from disk, by number, if we need to perform
2185 // replication checks. This adds an additional database read, so if the
2186 // caller already has the message in memory then it should be supplied. (Obviously
2187 // this mode of operation only works if we're saving a single message.)
2189 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2190 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2193 char hold_rm[ROOMNAMELEN];
2194 struct cdbdata *cdbfr;
2197 long highest_msg = 0L;
2200 struct CtdlMessage *msg = NULL;
2202 long *msgs_to_be_merged = NULL;
2203 int num_msgs_to_be_merged = 0;
2206 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2207 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2210 strcpy(hold_rm, CC->room.QRname);
2213 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2214 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2215 if (num_newmsgs > 1) supplied_msg = NULL;
2217 /* Now the regular stuff */
2218 if (CtdlGetRoomLock(&CC->room,
2219 ((roomname != NULL) ? roomname : CC->room.QRname) )
2221 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2222 return(ERROR + ROOM_NOT_FOUND);
2225 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2226 num_msgs_to_be_merged = 0;
2227 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2229 /* Create a list of msgid's which were supplied by the caller, but do
2230 * not already exist in the target room. It is absolutely taboo to
2231 * have more than one reference to the same message in a room.
2233 for (i=0; i<num_newmsgs; ++i) {
2235 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2236 if (msglist[j] == newmsgidlist[i]) {
2241 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2245 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2248 * Now merge the new messages
2250 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2251 if (msglist == NULL) {
2252 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2253 free(msgs_to_be_merged);
2254 return (ERROR + INTERNAL_ERROR);
2256 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2257 num_msgs += num_msgs_to_be_merged;
2259 /* Sort the message list, so all the msgid's are in order */
2260 num_msgs = sort_msglist(msglist, num_msgs);
2262 /* Determine the highest message number */
2263 highest_msg = msglist[num_msgs - 1];
2265 /* Write it back to disk. */
2266 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2268 /* Free up the memory we used. */
2271 /* Update the highest-message pointer and unlock the room. */
2272 CC->room.QRhighest = highest_msg;
2273 CtdlPutRoomLock(&CC->room);
2275 /* Perform replication checks if necessary */
2276 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2277 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2279 for (i=0; i<num_msgs_to_be_merged; ++i) {
2280 msgid = msgs_to_be_merged[i];
2282 if (supplied_msg != NULL) {
2286 msg = CtdlFetchMessage(msgid, 0);
2290 ReplicationChecks(msg);
2292 /* If the message has an Exclusive ID, index that... */
2293 if (!CM_IsEmpty(msg, eExclusiveID)) {
2294 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2297 /* Free up the memory we may have allocated */
2298 if (msg != supplied_msg) {
2307 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2310 /* Submit this room for processing by hooks */
2311 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2312 if (total_roomhook_errors) {
2313 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2316 /* Go back to the room we were in before we wandered here... */
2317 CtdlGetRoom(&CC->room, hold_rm);
2319 /* Bump the reference count for all messages which were merged */
2320 if (!suppress_refcount_adj) {
2321 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2324 /* Free up memory... */
2325 if (msgs_to_be_merged != NULL) {
2326 free(msgs_to_be_merged);
2329 /* Return success. */
2335 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2338 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
2339 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2344 * Message base operation to save a new message to the message store
2345 * (returns new message number)
2347 * This is the back end for CtdlSubmitMsg() and should not be directly
2348 * called by server-side modules.
2351 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2359 * If the message is big, set its body aside for storage elsewhere
2360 * and we hide the message body from the serializer
2362 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2364 holdM = msg->cm_fields[eMesageText];
2365 msg->cm_fields[eMesageText] = NULL;
2366 holdMLen = msg->cm_lengths[eMesageText];
2367 msg->cm_lengths[eMesageText] = 0;
2370 /* Serialize our data structure for storage in the database */
2371 CtdlSerializeMessage(&smr, msg);
2374 /* put the message body back into the message */
2375 msg->cm_fields[eMesageText] = holdM;
2376 msg->cm_lengths[eMesageText] = holdMLen;
2381 cprintf("%d Unable to serialize message\n",
2382 ERROR + INTERNAL_ERROR);
2385 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2391 /* Write our little bundle of joy into the message base */
2392 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2394 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2398 retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
2400 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2405 /* Free the memory we used for the serialized message */
2411 long send_message(struct CtdlMessage *msg) {
2417 /* Get a new message number */
2418 newmsgid = get_new_message_number();
2420 /* Generate an ID if we don't have one already */
2421 if (CM_IsEmpty(msg, emessageId)) {
2422 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2423 (long unsigned int) time(NULL),
2424 (long unsigned int) newmsgid,
2425 CtdlGetConfigStr("c_fqdn")
2428 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2431 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2437 /* Return the *local* message ID to the caller
2438 * (even if we're storing an incoming network message)
2445 * Serialize a struct CtdlMessage into the format used on disk.
2447 * This function loads up a "struct ser_ret" (defined in server.h) which
2448 * contains the length of the serialized message and a pointer to the
2449 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2451 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2452 struct CtdlMessage *msg) /* unserialized msg */
2458 * Check for valid message format
2460 if (CM_IsValidMsg(msg) == 0) {
2461 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2468 for (i=0; i < NDiskFields; ++i)
2469 if (msg->cm_fields[FieldOrder[i]] != NULL)
2470 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2472 ret->ser = malloc(ret->len);
2473 if (ret->ser == NULL) {
2474 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2481 ret->ser[1] = msg->cm_anon_type;
2482 ret->ser[2] = msg->cm_format_type;
2485 for (i=0; i < NDiskFields; ++i) {
2486 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2487 ret->ser[wlen++] = (char)FieldOrder[i];
2489 memcpy(&ret->ser[wlen],
2490 msg->cm_fields[FieldOrder[i]],
2491 msg->cm_lengths[FieldOrder[i]] + 1);
2493 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2497 if (ret->len != wlen) {
2498 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2506 * Check to see if any messages already exist in the current room which
2507 * carry the same Exclusive ID as this one. If any are found, delete them.
2509 void ReplicationChecks(struct CtdlMessage *msg) {
2510 long old_msgnum = (-1L);
2512 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2514 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2516 /* No exclusive id? Don't do anything. */
2517 if (msg == NULL) return;
2518 if (CM_IsEmpty(msg, eExclusiveID)) return;
2520 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2521 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2523 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2524 if (old_msgnum > 0L) {
2525 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2526 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2532 * Save a message to disk and submit it into the delivery system.
2534 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2535 struct recptypes *recps, /* recipients (if mail) */
2536 const char *force /* force a particular room? */
2538 char hold_rm[ROOMNAMELEN];
2539 char actual_rm[ROOMNAMELEN];
2540 char force_room[ROOMNAMELEN];
2541 char content_type[SIZ]; /* We have to learn this */
2542 char recipient[SIZ];
2543 char bounce_to[1024];
2546 const char *mptr = NULL;
2547 struct ctdluser userbuf;
2549 struct MetaData smi;
2550 char *collected_addresses = NULL;
2551 struct addresses_to_be_filed *aptr = NULL;
2552 StrBuf *saved_rfc822_version = NULL;
2553 int qualified_for_journaling = 0;
2555 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2556 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2558 /* If this message has no timestamp, we take the liberty of
2559 * giving it one, right now.
2561 if (CM_IsEmpty(msg, eTimestamp)) {
2562 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2565 /* If this message has no path, we generate one.
2567 if (CM_IsEmpty(msg, eMessagePath)) {
2568 if (!CM_IsEmpty(msg, eAuthor)) {
2569 CM_CopyField(msg, eMessagePath, eAuthor);
2570 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2571 if (isspace(msg->cm_fields[eMessagePath][a])) {
2572 msg->cm_fields[eMessagePath][a] = ' ';
2577 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2581 if (force == NULL) {
2582 force_room[0] = '\0';
2585 strcpy(force_room, force);
2588 /* Learn about what's inside, because it's what's inside that counts */
2589 if (CM_IsEmpty(msg, eMesageText)) {
2590 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2594 switch (msg->cm_format_type) {
2596 strcpy(content_type, "text/x-citadel-variformat");
2599 strcpy(content_type, "text/plain");
2602 strcpy(content_type, "text/plain");
2603 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2606 safestrncpy(content_type, &mptr[13], sizeof content_type);
2607 string_trim(content_type);
2608 aptr = content_type;
2609 while (!IsEmptyStr(aptr)) {
2621 /* Goto the correct room */
2622 room = (recps) ? CC->room.QRname : SENTITEMS;
2623 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2624 strcpy(hold_rm, CC->room.QRname);
2625 strcpy(actual_rm, CC->room.QRname);
2626 if (recps != NULL) {
2627 strcpy(actual_rm, SENTITEMS);
2630 /* If the user is a twit, move to the twit room for posting */
2632 if (CC->user.axlevel == AxProbU) {
2633 strcpy(hold_rm, actual_rm);
2634 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2635 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2639 /* ...or if this message is destined for Aide> then go there. */
2640 if (!IsEmptyStr(force_room)) {
2641 strcpy(actual_rm, force_room);
2644 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2645 if (strcasecmp(actual_rm, CC->room.QRname)) {
2646 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2650 * If this message has no O (room) field, generate one.
2652 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2653 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2656 /* Perform "before save" hooks (aborting if any return nonzero) */
2657 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2658 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2661 * If this message has an Exclusive ID, and the room is replication
2662 * checking enabled, then do replication checks.
2664 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2665 ReplicationChecks(msg);
2668 /* Save it to disk */
2669 syslog(LOG_DEBUG, "msgbase: saving to disk");
2670 newmsgid = send_message(msg);
2671 if (newmsgid <= 0L) return(-5);
2673 /* Write a supplemental message info record. This doesn't have to
2674 * be a critical section because nobody else knows about this message
2677 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2678 memset(&smi, 0, sizeof(struct MetaData));
2679 smi.meta_msgnum = newmsgid;
2680 smi.meta_refcount = 0;
2681 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2684 * Measure how big this message will be when rendered as RFC822.
2685 * We do this for two reasons:
2686 * 1. We need the RFC822 length for the new metadata record, so the
2687 * POP and IMAP services don't have to calculate message lengths
2688 * while the user is waiting (multiplied by potentially hundreds
2689 * or thousands of messages).
2690 * 2. If journaling is enabled, we will need an RFC822 version of the
2691 * message to attach to the journalized copy.
2693 if (CC->redirect_buffer != NULL) {
2694 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2695 exit(CTDLEXIT_REDIRECT);
2697 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2698 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2699 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2700 saved_rfc822_version = CC->redirect_buffer;
2701 CC->redirect_buffer = NULL;
2705 /* Now figure out where to store the pointers */
2706 syslog(LOG_DEBUG, "msgbase: storing pointers");
2708 /* If this is being done by the networker delivering a private
2709 * message, we want to BYPASS saving the sender's copy (because there
2710 * is no local sender; it would otherwise go to the Trashcan).
2712 if ((!CC->internal_pgm) || (recps == NULL)) {
2713 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2714 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2715 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2719 /* For internet mail, drop a copy in the outbound queue room */
2720 if ((recps != NULL) && (recps->num_internet > 0)) {
2721 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2724 /* If other rooms are specified, drop them there too. */
2725 if ((recps != NULL) && (recps->num_room > 0)) {
2726 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2727 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2728 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2729 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2733 /* Decide where bounces need to be delivered */
2734 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2735 if (CC->logged_in) {
2736 strcpy(bounce_to, CC->user.fullname);
2738 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2739 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2741 recps->bounce_to = bounce_to;
2744 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2746 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2747 if ((recps != NULL) && (recps->num_local > 0)) {
2751 pch = recps->recp_local;
2752 recps->recp_local = recipient;
2753 ntokens = num_tokens(pch, '|');
2754 for (i=0; i<ntokens; ++i) {
2755 extract_token(recipient, pch, i, '|', sizeof recipient);
2756 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2757 if (CtdlGetUser(&userbuf, recipient) == 0) {
2758 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2759 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2760 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2761 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2764 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2765 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2768 recps->recp_local = pch;
2771 /* Perform "after save" hooks */
2772 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2774 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2775 CM_FlushField(msg, eVltMsgNum);
2777 /* Go back to the room we started from */
2778 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2779 if (strcasecmp(hold_rm, CC->room.QRname)) {
2780 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2784 * Any addresses to harvest for someone's address book?
2786 if ( (CC->logged_in) && (recps != NULL) ) {
2787 collected_addresses = harvest_collected_addresses(msg);
2790 if (collected_addresses != NULL) {
2791 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2792 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2793 aptr->roomname = strdup(actual_rm);
2794 aptr->collected_addresses = collected_addresses;
2795 begin_critical_section(S_ATBF);
2798 end_critical_section(S_ATBF);
2802 * Determine whether this message qualifies for journaling.
2804 if (!CM_IsEmpty(msg, eJournal)) {
2805 qualified_for_journaling = 0;
2808 if (recps == NULL) {
2809 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2811 else if (recps->num_local + recps->num_internet > 0) {
2812 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2815 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2820 * Do we have to perform journaling? If so, hand off the saved
2821 * RFC822 version will be handed off to the journaler for background
2822 * submit. Otherwise, we have to free the memory ourselves.
2824 if (saved_rfc822_version != NULL) {
2825 if (qualified_for_journaling) {
2826 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2829 FreeStrBuf(&saved_rfc822_version);
2833 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2834 recps->bounce_to = NULL;
2842 * Convenience function for generating small administrative messages.
2844 long quickie_message(char *from,
2852 struct CtdlMessage *msg;
2853 struct recptypes *recp = NULL;
2855 msg = malloc(sizeof(struct CtdlMessage));
2856 memset(msg, 0, sizeof(struct CtdlMessage));
2857 msg->cm_magic = CTDLMESSAGE_MAGIC;
2858 msg->cm_anon_type = MES_NORMAL;
2859 msg->cm_format_type = format_type;
2861 if (!IsEmptyStr(from)) {
2862 CM_SetField(msg, eAuthor, from, -1);
2864 else if (!IsEmptyStr(fromaddr)) {
2866 CM_SetField(msg, eAuthor, fromaddr, -1);
2867 pAt = strchr(msg->cm_fields[eAuthor], '@');
2869 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2873 msg->cm_fields[eAuthor] = strdup("Citadel");
2876 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
2877 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
2878 if (!IsEmptyStr(to)) {
2879 CM_SetField(msg, eRecipient, to, -1);
2880 recp = validate_recipients(to, NULL, 0);
2882 if (!IsEmptyStr(subject)) {
2883 CM_SetField(msg, eMsgSubject, subject, -1);
2885 if (!IsEmptyStr(text)) {
2886 CM_SetField(msg, eMesageText, text, -1);
2889 long msgnum = CtdlSubmitMsg(msg, recp, room);
2891 if (recp != NULL) free_recipients(recp);
2897 * Back end function used by CtdlMakeMessage() and similar functions
2899 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2901 size_t maxlen, // maximum message length
2902 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2903 int crlf // CRLF newlines instead of LF
2911 LineBuf = NewStrBufPlain(NULL, SIZ);
2912 if (exist == NULL) {
2913 Message = NewStrBufPlain(NULL, 4 * SIZ);
2916 Message = NewStrBufDup(exist);
2919 /* Do we need to change leading ".." to "." for SMTP escaping? */
2920 if ((tlen == 1) && (*terminator == '.')) {
2924 /* read in the lines of message text one by one */
2926 if (CtdlClientGetLine(LineBuf) < 0) {
2929 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2932 if ( (!flushing) && (!finished) ) {
2934 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2937 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2940 /* Unescape SMTP-style input of two dots at the beginning of the line */
2941 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2942 StrBufCutLeft(LineBuf, 1);
2944 StrBufAppendBuf(Message, LineBuf, 0);
2947 /* if we've hit the max msg length, flush the rest */
2948 if (StrLength(Message) >= maxlen) {
2952 } while (!finished);
2953 FreeStrBuf(&LineBuf);
2956 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2963 // Back end function used by CtdlMakeMessage() and similar functions
2964 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2966 size_t maxlen, // maximum message length
2967 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2968 int crlf // CRLF newlines instead of LF
2972 Message = CtdlReadMessageBodyBuf(terminator,
2978 if (Message == NULL) {
2982 return SmashStrBuf(&Message);
2987 struct CtdlMessage *CtdlMakeMessage(
2988 struct ctdluser *author, /* author's user structure */
2989 char *recipient, /* NULL if it's not mail */
2990 char *recp_cc, /* NULL if it's not mail */
2991 char *room, /* room where it's going */
2992 int type, /* see MES_ types in header file */
2993 int format_type, /* variformat, plain text, MIME... */
2994 char *fake_name, /* who we're masquerading as */
2995 char *my_email, /* which of my email addresses to use (empty is ok) */
2996 char *subject, /* Subject (optional) */
2997 char *supplied_euid, /* ...or NULL if this is irrelevant */
2998 char *preformatted_text, /* ...or NULL to read text from client */
2999 char *references /* Thread references */
3001 return CtdlMakeMessageLen(
3002 author, /* author's user structure */
3003 recipient, /* NULL if it's not mail */
3004 (recipient)?strlen(recipient) : 0,
3005 recp_cc, /* NULL if it's not mail */
3006 (recp_cc)?strlen(recp_cc): 0,
3007 room, /* room where it's going */
3008 (room)?strlen(room): 0,
3009 type, /* see MES_ types in header file */
3010 format_type, /* variformat, plain text, MIME... */
3011 fake_name, /* who we're masquerading as */
3012 (fake_name)?strlen(fake_name): 0,
3013 my_email, /* which of my email addresses to use (empty is ok) */
3014 (my_email)?strlen(my_email): 0,
3015 subject, /* Subject (optional) */
3016 (subject)?strlen(subject): 0,
3017 supplied_euid, /* ...or NULL if this is irrelevant */
3018 (supplied_euid)?strlen(supplied_euid):0,
3019 preformatted_text, /* ...or NULL to read text from client */
3020 (preformatted_text)?strlen(preformatted_text) : 0,
3021 references, /* Thread references */
3022 (references)?strlen(references):0);
3028 * Build a binary message to be saved on disk.
3029 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3030 * will become part of the message. This means you are no longer
3031 * responsible for managing that memory -- it will be freed along with
3032 * the rest of the fields when CM_Free() is called.)
3034 struct CtdlMessage *CtdlMakeMessageLen(
3035 struct ctdluser *author, /* author's user structure */
3036 char *recipient, /* NULL if it's not mail */
3038 char *recp_cc, /* NULL if it's not mail */
3040 char *room, /* room where it's going */
3042 int type, /* see MES_ types in header file */
3043 int format_type, /* variformat, plain text, MIME... */
3044 char *fake_name, /* who we're masquerading as */
3046 char *my_email, /* which of my email addresses to use (empty is ok) */
3048 char *subject, /* Subject (optional) */
3050 char *supplied_euid, /* ...or NULL if this is irrelevant */
3052 char *preformatted_text, /* ...or NULL to read text from client */
3054 char *references, /* Thread references */
3059 struct CtdlMessage *msg;
3061 StrBuf *FakeEncAuthor = NULL;
3063 msg = malloc(sizeof(struct CtdlMessage));
3064 memset(msg, 0, sizeof(struct CtdlMessage));
3065 msg->cm_magic = CTDLMESSAGE_MAGIC;
3066 msg->cm_anon_type = type;
3067 msg->cm_format_type = format_type;
3069 if (recipient != NULL) rcplen = string_trim(recipient);
3070 if (recp_cc != NULL) cclen = string_trim(recp_cc);
3072 /* Path or Return-Path */
3074 CM_SetField(msg, eMessagePath, my_email, myelen);
3076 else if (!IsEmptyStr(author->fullname)) {
3077 CM_SetField(msg, eMessagePath, author->fullname, -1);
3079 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3081 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3082 CM_SetField(msg, eTimestamp, buf, blen);
3085 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3088 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3090 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3091 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3092 FreeStrBuf(&FakeAuthor);
3094 if (!!IsEmptyStr(CC->room.QRname)) {
3095 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3096 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3099 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3104 CM_SetField(msg, eRecipient, recipient, rcplen);
3107 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3111 CM_SetField(msg, erFc822Addr, my_email, myelen);
3113 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3114 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3117 if (subject != NULL) {
3119 length = string_trim(subject);
3125 while ((subject[i] != '\0') &&
3126 (IsAscii = isascii(subject[i]) != 0 ))
3129 CM_SetField(msg, eMsgSubject, subject, subjlen);
3130 else /* ok, we've got utf8 in the string. */
3133 rfc2047Subj = rfc2047encode(subject, length);
3134 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3141 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3145 CM_SetField(msg, eWeferences, references, reflen);
3148 if (preformatted_text != NULL) {
3149 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3153 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3154 if (MsgBody != NULL) {
3155 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3164 * API function to delete messages which match a set of criteria
3165 * (returns the actual number of messages deleted)
3167 int CtdlDeleteMessages(const char *room_name, // which room
3168 long *dmsgnums, // array of msg numbers to be deleted
3169 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3170 char *content_type // or "" for any. regular expressions expected.
3172 struct ctdlroom qrbuf;
3173 struct cdbdata *cdbfr;
3174 long *msglist = NULL;
3175 long *dellist = NULL;
3178 int num_deleted = 0;
3180 struct MetaData smi;
3183 int need_to_free_re = 0;
3185 if (content_type) if (!IsEmptyStr(content_type)) {
3186 regcomp(&re, content_type, 0);
3187 need_to_free_re = 1;
3189 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3191 /* get room record, obtaining a lock... */
3192 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3193 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3194 if (need_to_free_re) regfree(&re);
3195 return(0); /* room not found */
3198 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3200 dellist = malloc(num_msgs * sizeof(long));
3201 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3202 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3203 int have_more_del = 1;
3205 num_msgs = sort_msglist(msglist, num_msgs);
3206 if (num_dmsgnums > 1) {
3207 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3211 while ((i < num_msgs) && (have_more_del)) {
3214 /* Set/clear a bit for each criterion */
3216 /* 0 messages in the list or a null list means that we are
3217 * interested in deleting any messages which meet the other criteria.
3220 delete_this |= 0x01;
3223 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3228 if (msglist[i] == dmsgnums[j]) {
3229 delete_this |= 0x01;
3232 have_more_del = (j < num_dmsgnums);
3235 if (have_contenttype) {
3236 GetMetaData(&smi, msglist[i]);
3237 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3238 delete_this |= 0x02;
3241 delete_this |= 0x02;
3244 /* Delete message only if all bits are set */
3245 if (delete_this == 0x03) {
3246 dellist[num_deleted++] = msglist[i];
3252 num_msgs = sort_msglist(msglist, num_msgs);
3253 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3256 qrbuf.QRhighest = msglist[num_msgs - 1];
3259 qrbuf.QRhighest = 0;
3262 CtdlPutRoomLock(&qrbuf);
3264 /* Go through the messages we pulled out of the index, and decrement
3265 * their reference counts by 1. If this is the only room the message
3266 * was in, the reference count will reach zero and the message will
3267 * automatically be deleted from the database. We do this in a
3268 * separate pass because there might be plug-in hooks getting called,
3269 * and we don't want that happening during an S_ROOMS critical
3273 for (i=0; i<num_deleted; ++i) {
3274 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3276 AdjRefCountList(dellist, num_deleted, -1);
3278 /* Now free the memory we used, and go away. */
3279 if (msglist != NULL) free(msglist);
3280 if (dellist != NULL) free(dellist);
3281 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3282 if (need_to_free_re) regfree(&re);
3283 return (num_deleted);
3288 * GetMetaData() - Get the supplementary record for a message
3290 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3291 struct cdbdata cdbsmi;
3294 memset(smibuf, 0, sizeof(struct MetaData));
3295 smibuf->meta_msgnum = msgnum;
3296 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3298 /* Use the negative of the message number for its supp record index */
3299 TheIndex = (0L - msgnum);
3301 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3302 if (cdbsmi.ptr == NULL) {
3303 return; /* record not found; leave it alone */
3305 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3311 * PutMetaData() - (re)write supplementary record for a message
3313 void PutMetaData(struct MetaData *smibuf)
3317 /* Use the negative of the message number for the metadata db index */
3318 TheIndex = (0L - smibuf->meta_msgnum);
3320 cdb_store(CDB_MSGMAIN,
3321 &TheIndex, (int)sizeof(long),
3322 smibuf, (int)sizeof(struct MetaData)
3327 // Convenience function to process a big block of AdjRefCount() operations
3328 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3331 for (i = 0; i < nmsg; i++) {
3332 AdjRefCount(msgnum[i], incr);
3337 // AdjRefCount - adjust the reference count for a message.
3338 // We need to delete from disk any message whose reference count reaches zero.
3339 void AdjRefCount(long msgnum, int incr) {
3340 struct MetaData smi;
3343 // This is a *tight* critical section; please keep it that way, as
3344 // it may get called while nested in other critical sections.
3345 // Complicating this any further will surely cause deadlock!
3346 begin_critical_section(S_SUPPMSGMAIN);
3347 GetMetaData(&smi, msgnum);
3348 smi.meta_refcount += incr;
3350 end_critical_section(S_SUPPMSGMAIN);
3351 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3353 // If the reference count is now zero, delete both the message and its metadata record.
3354 if (smi.meta_refcount == 0) {
3355 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3357 // Call delete hooks with NULL room to show it has gone altogether
3358 PerformDeleteHooks(NULL, msgnum);
3360 // Remove from message base
3362 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3363 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3365 // Remove metadata record
3366 delnum = (0L - msgnum);
3367 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3372 // Write a generic object to this room
3373 // Returns the message number of the written object, in case you need it.
3374 long CtdlWriteObject(char *req_room, // Room to stuff it in
3375 char *content_type, // MIME type of this object
3376 char *raw_message, // Data to be written
3377 off_t raw_length, // Size of raw_message
3378 struct ctdluser *is_mailbox, // Mailbox room?
3379 int is_binary, // Is encoding necessary?
3380 unsigned int flags // Internal save flags
3382 struct ctdlroom qrbuf;
3383 char roomname[ROOMNAMELEN];
3384 struct CtdlMessage *msg;
3385 StrBuf *encoded_message = NULL;
3387 if (is_mailbox != NULL) {
3388 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3391 safestrncpy(roomname, req_room, sizeof(roomname));
3394 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3397 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3400 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3403 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3404 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3405 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3408 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3411 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3415 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3418 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3421 syslog(LOG_DEBUG, "msgbase: allocating");
3422 msg = malloc(sizeof(struct CtdlMessage));
3423 memset(msg, 0, sizeof(struct CtdlMessage));
3424 msg->cm_magic = CTDLMESSAGE_MAGIC;
3425 msg->cm_anon_type = MES_NORMAL;
3426 msg->cm_format_type = 4;
3427 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3428 CM_SetField(msg, eOriginalRoom, req_room, -1);
3429 msg->cm_flags = flags;
3431 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3433 /* Create the requested room if we have to. */
3434 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3435 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3438 /* Now write the data */
3439 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3445 /************************************************************************/
3446 /* MODULE INITIALIZATION */
3447 /************************************************************************/
3449 char *ctdl_module_init_msgbase(void) {
3451 FillMsgKeyLookupTable();
3454 /* return our module id for the log */