1 // Implements the message store.
3 // Copyright (c) 1987-2024 by the citadel.org team
5 // This program is open source software. Use, duplication, or disclosure
6 // is subject to 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"
27 #include "modules/fulltext/serv_fulltext.h"
29 struct addresses_to_be_filed *atbf = NULL;
31 // These are the four-character field headers we use when outputting
32 // messages in Citadel format (as opposed to RFC822 format).
34 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
35 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
36 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
37 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
38 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
40 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
41 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
43 "from", // A -> eAuthor
44 NULL, // B -> eBig_message
45 NULL, // C (formerly used as eRemoteRoom)
46 NULL, // D (formerly used as eDestination)
47 "exti", // E -> eXclusivID
48 "rfca", // F -> erFc822Addr
50 "hnod", // H (formerly used as eHumanNode)
51 "msgn", // I -> emessageId
52 "jrnl", // J -> eJournal
53 "rep2", // K -> eReplyTo
54 "list", // L -> eListID
55 "text", // M -> eMessageText
56 NULL, // N (formerly used as eNodename)
57 "room", // O -> eOriginalRoom
58 "path", // P -> eMessagePath
60 "rcpt", // R -> eRecipient
61 NULL, // S (formerly used as eSpecialField)
62 "time", // T -> eTimestamp
63 "subj", // U -> eMsgSubject
64 "nvto", // V -> eenVelopeTo
65 "wefw", // W -> eWeferences
67 "cccc", // Y -> eCarbonCopY
72 HashList *msgKeyLookup = NULL;
74 int GetFieldFromMnemonic(eMsgField *f, const char* c) {
76 if (GetHash(msgKeyLookup, c, 4, &v)) {
83 void FillMsgKeyLookupTable(void) {
86 msgKeyLookup = NewHash (1, FourHash);
88 for (i=0; i < 91; i++) {
89 if (msgkeys[i] != NULL) {
90 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
96 eMsgField FieldOrder[] = {
105 // Semi-important fields
123 // Message text (MUST be last)
125 // Not saved to disk:
130 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
133 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
134 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
138 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf) {
139 if (Msg->cm_fields[which] != NULL) {
140 free(Msg->cm_fields[which]);
142 Msg->cm_fields[which] = strdup(buf);
143 Msg->cm_lengths[which] = strlen(buf);
147 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
150 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
151 CM_SetField(Msg, which, buf);
155 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
156 if (Msg->cm_fields[WhichToCut] == NULL)
159 if (Msg->cm_lengths[WhichToCut] > maxlen)
161 Msg->cm_fields[WhichToCut][maxlen] = '\0';
162 Msg->cm_lengths[WhichToCut] = maxlen;
167 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
168 if (Msg->cm_fields[which] != NULL)
169 free (Msg->cm_fields[which]);
170 Msg->cm_fields[which] = NULL;
171 Msg->cm_lengths[which] = 0;
175 void CM_Flush(struct CtdlMessage *Msg) {
178 if (CM_IsValidMsg(Msg) == 0) {
182 for (i = 0; i < 256; ++i) {
183 CM_FlushField(Msg, i);
188 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
190 if (Msg->cm_fields[WhichToPutTo] != NULL) {
191 free (Msg->cm_fields[WhichToPutTo]);
194 if (Msg->cm_fields[WhichtToCopy] != NULL) {
195 len = Msg->cm_lengths[WhichtToCopy];
196 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
197 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
198 Msg->cm_fields[WhichToPutTo][len] = '\0';
199 Msg->cm_lengths[WhichToPutTo] = len;
202 Msg->cm_fields[WhichToPutTo] = NULL;
203 Msg->cm_lengths[WhichToPutTo] = 0;
208 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
209 if (Msg->cm_fields[which] != NULL) {
214 oldmsgsize = Msg->cm_lengths[which] + 1;
215 newmsgsize = length + oldmsgsize;
217 new = malloc(newmsgsize);
218 memcpy(new, buf, length);
219 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
220 free(Msg->cm_fields[which]);
221 Msg->cm_fields[which] = new;
222 Msg->cm_lengths[which] = newmsgsize - 1;
225 Msg->cm_fields[which] = malloc(length + 1);
226 memcpy(Msg->cm_fields[which], buf, length);
227 Msg->cm_fields[which][length] = '\0';
228 Msg->cm_lengths[which] = length;
233 // This is like CM_SetField() except the caller is transferring ownership of the supplied memory to the message
234 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
235 if (Msg->cm_fields[which] != NULL) {
236 free (Msg->cm_fields[which]);
239 Msg->cm_fields[which] = *buf;
241 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
242 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
245 Msg->cm_lengths[which] = length;
250 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
251 if (Msg->cm_fields[which] != NULL) {
252 free (Msg->cm_fields[which]);
255 Msg->cm_lengths[which] = StrLength(*buf);
256 Msg->cm_fields[which] = SmashStrBuf(buf);
260 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
261 if (Msg->cm_fields[which] != NULL) {
262 *retlen = Msg->cm_lengths[which];
263 *ret = Msg->cm_fields[which];
264 Msg->cm_fields[which] = NULL;
265 Msg->cm_lengths[which] = 0;
274 // Returns 1 if the supplied pointer points to a valid Citadel message.
275 // If the pointer is NULL or the magic number check fails, returns 0.
276 int CM_IsValidMsg(struct CtdlMessage *msg) {
280 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
281 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
288 void CM_FreeContents(struct CtdlMessage *msg) {
291 for (i = 0; i < 256; ++i)
292 if (msg->cm_fields[i] != NULL) {
293 free(msg->cm_fields[i]);
294 msg->cm_lengths[i] = 0;
297 msg->cm_magic = 0; // just in case
301 // 'Destructor' for struct CtdlMessage
302 void CM_Free(struct CtdlMessage *msg) {
303 if (CM_IsValidMsg(msg) == 0) {
304 if (msg != NULL) free (msg);
307 CM_FreeContents(msg);
312 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
314 len = OrgMsg->cm_lengths[i];
315 NewMsg->cm_fields[i] = malloc(len + 1);
316 if (NewMsg->cm_fields[i] == NULL) {
319 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
320 NewMsg->cm_fields[i][len] = '\0';
321 NewMsg->cm_lengths[i] = len;
326 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
328 struct CtdlMessage *NewMsg;
330 if (CM_IsValidMsg(OrgMsg) == 0) {
333 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
334 if (NewMsg == NULL) {
338 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
340 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
342 for (i = 0; i < 256; ++i) {
343 if (OrgMsg->cm_fields[i] != NULL) {
344 if (!CM_DupField(i, OrgMsg, NewMsg)) {
355 // Determine if a given message matches the fields in a message template.
356 // Return 0 for a successful match.
357 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
360 // If there aren't any fields in the template, all messages will match.
361 if (template == NULL) return(0);
363 // Null messages are bogus.
364 if (msg == NULL) return(1);
366 for (i='A'; i<='Z'; ++i) {
367 if (template->cm_fields[i] != NULL) {
368 if (msg->cm_fields[i] == NULL) {
369 // Considered equal if temmplate is empty string
370 if (IsEmptyStr(template->cm_fields[i])) continue;
373 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
374 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
379 // All compares succeeded: we have a match!
384 // Retrieve the "seen" message list for the current room.
385 void CtdlGetSeen(char *buf, int which_set) {
388 // Learn about the user and room in question
389 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
391 if (which_set == ctdlsetseen_seen) {
392 safestrncpy(buf, vbuf.v_seen, SIZ);
394 if (which_set == ctdlsetseen_answered) {
395 safestrncpy(buf, vbuf.v_answered, SIZ);
400 // Manipulate the "seen msgs" string (or other message set strings)
401 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
402 int target_setting, int which_set,
403 struct ctdluser *which_user, struct ctdlroom *which_room) {
417 char *is_set; // actually an array of booleans
419 // Don't bother doing *anything* if we were passed a list of zero messages
420 if (num_target_msgnums < 1) {
424 // If no room was specified, we go with the current room.
426 which_room = &CC->room;
429 // If no user was specified, we go with the current user.
431 which_user = &CC->user;
434 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
435 num_target_msgnums, target_msgnums[0],
436 (target_setting ? "SET" : "CLEAR"),
440 // Learn about the user and room in question
441 CtdlGetRelationship(&vbuf, which_user, which_room);
443 // Load the message list
444 num_msgs = CtdlFetchMsgList(which_room->QRnumber, &msglist);
450 is_set = malloc(num_msgs * sizeof(char));
451 memset(is_set, 0, (num_msgs * sizeof(char)) );
453 // Decide which message set we're manipulating
455 case ctdlsetseen_seen:
456 vset = NewStrBufPlain(vbuf.v_seen, -1);
458 case ctdlsetseen_answered:
459 vset = NewStrBufPlain(vbuf.v_answered, -1);
465 // Translate the existing sequence set into an array of booleans
466 setstr = NewStrBuf();
470 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
472 StrBufExtract_token(lostr, setstr, 0, ':');
473 if (StrBufNum_tokens(setstr, ':') >= 2) {
474 StrBufExtract_token(histr, setstr, 1, ':');
478 StrBufAppendBuf(histr, lostr, 0);
481 if (!strcmp(ChrPtr(histr), "*")) {
488 for (i = 0; i < num_msgs; ++i) {
489 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
498 // Now translate the array of booleans back into a sequence set
504 for (i=0; i<num_msgs; ++i) {
508 for (k=0; k<num_target_msgnums; ++k) {
509 if (msglist[i] == target_msgnums[k]) {
510 is_seen = target_setting;
514 if ((was_seen == 0) && (is_seen == 1)) {
517 else if ((was_seen == 1) && (is_seen == 0)) {
520 if (StrLength(vset) > 0) {
521 StrBufAppendBufPlain(vset, HKEY(","), 0);
524 StrBufAppendPrintf(vset, "%ld", hi);
527 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
531 if ((is_seen) && (i == num_msgs - 1)) {
532 if (StrLength(vset) > 0) {
533 StrBufAppendBufPlain(vset, HKEY(","), 0);
535 if ((i==0) || (was_seen == 0)) {
536 StrBufAppendPrintf(vset, "%ld", msglist[i]);
539 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
546 // We will have to stuff this string back into a 4096 byte buffer, so if it's
547 // larger than that now, truncate it by removing tokens from the beginning.
548 // The limit of 100 iterations is there to prevent an infinite loop in case
549 // something unexpected happens.
550 int number_of_truncations = 0;
551 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
552 StrBufRemove_token(vset, 0, ',');
553 ++number_of_truncations;
556 // If we're truncating the sequence set of messages marked with the 'seen' flag,
557 // we want the earliest messages (the truncated ones) to be marked, not unmarked.
558 // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
559 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
561 first_tok = NewStrBuf();
562 StrBufExtract_token(first_tok, vset, 0, ',');
563 StrBufRemove_token(vset, 0, ',');
565 if (StrBufNum_tokens(first_tok, ':') > 1) {
566 StrBufRemove_token(first_tok, 0, ':');
570 new_set = NewStrBuf();
571 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
572 StrBufAppendBuf(new_set, first_tok, 0);
573 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
574 StrBufAppendBuf(new_set, vset, 0);
577 FreeStrBuf(&first_tok);
581 // Decide which message set we're manipulating. Zero the buffers so they compress well.
583 case ctdlsetseen_seen:
584 memset(vbuf.v_seen, 0, sizeof vbuf.v_seen);
585 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
587 case ctdlsetseen_answered:
588 memset(vbuf.v_answered, 0, sizeof vbuf.v_seen);
589 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
595 CtdlSetRelationship(&vbuf, which_user, which_room);
600 // API function to perform an operation for each qualifying message in the
601 // current room. (Returns the number of messages processed.)
602 int CtdlForEachMessage(int mode, long ref, char *search_string,
604 struct CtdlMessage *compare,
605 ForEachMsgCallback CallBack,
610 long *msglist = NULL;
612 int num_processed = 0;
615 struct CtdlMessage *msg = NULL;
618 int printed_lastold = 0;
620 int need_to_free_re = 0;
622 Array *search = NULL;
624 if ((content_type) && (!IsEmptyStr(content_type))) {
625 regcomp(&re, content_type, 0);
629 // Learn about the user and room in question
630 if (server_shutting_down) {
631 if (need_to_free_re) regfree(&re);
634 CtdlGetUser(&CC->user, CC->curr_user);
636 if (server_shutting_down) {
637 if (need_to_free_re) regfree(&re);
640 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
642 if (server_shutting_down) {
643 if (need_to_free_re) regfree(&re);
647 // Load the message list
648 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
651 if (need_to_free_re) regfree(&re);
652 return 0; // No messages at all? No further action.
655 // Now begin the traversal.
656 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) if (msglist[a] > 0) {
658 // If the caller is looking for a specific MIME type, filter
659 // out all messages which are not of the type requested.
660 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
662 // This call to GetMetaData() sits inside this loop
663 // so that we only do the extra database read per msg
664 // if we need to. Doing the extra read all the time
665 // really kills the server. If we ever need to use
666 // metadata for another search criterion, we need to
667 // move the read somewhere else -- but still be smart
668 // enough to only do the read if the caller has
669 // specified something that will need it.
670 if (server_shutting_down) {
671 if (need_to_free_re) regfree(&re);
675 GetMetaData(&smi, msglist[a]);
677 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
683 num_msgs = sort_msglist(msglist, num_msgs);
685 // If a template was supplied, filter out the messages which don't match. (This could induce some delays!)
687 if (compare != NULL) {
688 for (a = 0; a < num_msgs; ++a) {
689 if (server_shutting_down) {
690 if (need_to_free_re) regfree(&re);
694 msg = CtdlFetchMessage(msglist[a], 1);
696 if (CtdlMsgCmp(msg, compare)) {
705 // If a search string was specified, get a message list from
706 // the full text index and remove messages which aren't on both
710 // Since the lists are sorted and strictly ascending, and the
711 // output list is guaranteed to be shorter than or equal to the
712 // input list, we overwrite the bottom of the input list. This
713 // eliminates the need to memmove big chunks of the list over and
715 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
717 // Call search module via hook mechanism.
718 // NULL means use any search function available.
719 // otherwise replace with a char * to name of search routine
720 search = CtdlFullTextSearch(search_string);
722 if (array_len(search) > 0) {
726 orig_num_msgs = num_msgs;
728 for (i=0; i<orig_num_msgs; ++i) {
729 for (j=0; j<array_len(search); ++j) {
731 memcpy(&smsgnum, array_get_element_at(search, j), sizeof(long));
732 if (msglist[i] == smsgnum) {
733 msglist[num_msgs++] = msglist[i];
739 num_msgs = 0; // No messages qualify
741 if (search != NULL) array_free(search);
743 // Now that we've purged messages which don't contain the search
744 // string, treat a MSGS_SEARCH just like a MSGS_ALL from this
749 // Now iterate through the message list, according to the
750 // criteria supplied by the caller.
752 for (a = 0; a < num_msgs; ++a) {
753 if (server_shutting_down) {
754 if (need_to_free_re) regfree(&re);
756 return num_processed;
758 thismsg = msglist[a];
759 if (mode == MSGS_ALL) {
763 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
764 if (is_seen) lastold = thismsg;
770 || ((mode == MSGS_OLD) && (is_seen))
771 || ((mode == MSGS_NEW) && (!is_seen))
772 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
773 || ((mode == MSGS_FIRST) && (a < ref))
774 || ((mode == MSGS_GT) && (thismsg > ref))
775 || ((mode == MSGS_LT) && (thismsg < ref))
776 || ((mode == MSGS_EQ) && (thismsg == ref))
779 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
781 CallBack(lastold, userdata);
787 CallBack(thismsg, userdata);
792 if (need_to_free_re) regfree(&re);
794 // We cache the most recent msglist in order to do security checks later
795 if (CC->client_socket > 0) {
796 if (CC->cached_msglist != NULL) {
797 free(CC->cached_msglist);
799 CC->cached_msglist = msglist;
800 CC->cached_num_msgs = num_msgs;
806 return num_processed;
810 // memfmout() - Citadel text formatter and paginator.
811 // Although the original purpose of this routine was to format
812 // text to the reader's screen width, all we're really using it
813 // for here is to format text out to 80 columns before sending it
814 // to the client. The client software MAY reformat it again.
816 char *mptr, // where are we going to get our text from?
817 const char *nl // string to terminate lines with
820 unsigned char ch = 0;
827 while (ch=*(mptr++), ch != 0) {
830 if (client_write(outbuf, len) == -1) {
831 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
835 if (client_write(nl, nllen) == -1) {
836 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
841 else if (ch == '\r') {
842 // Ignore carriage returns. Newlines are always LF or CRLF but never CR.
844 else if (isspace(ch)) {
845 if (column > 72) { // Beyond 72 columns, break on the next space
846 if (client_write(outbuf, len) == -1) {
847 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
851 if (client_write(nl, nllen) == -1) {
852 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
865 if (column > 1000) { // Beyond 1000 columns, break anywhere
866 if (client_write(outbuf, len) == -1) {
867 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
871 if (client_write(nl, nllen) == -1) {
872 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
880 if (client_write(outbuf, len) == -1) {
881 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
884 client_write(nl, nllen);
890 // Callback function for mime parser that simply lists the part
891 void list_this_part(char *name, char *filename, char *partnum, char *disp,
892 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
893 char *cbid, void *cbuserdata)
897 ma = (struct ma_info *)cbuserdata;
898 if (ma->is_ma == 0) {
899 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
912 // Callback function for multipart prefix
913 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
914 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
915 char *cbid, void *cbuserdata)
919 ma = (struct ma_info *)cbuserdata;
920 if (!strcasecmp(cbtype, "multipart/alternative")) {
924 if (ma->is_ma == 0) {
925 cprintf("pref=%s|%s\n", partnum, cbtype);
930 // Callback function for multipart sufffix
931 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
932 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
933 char *cbid, void *cbuserdata)
937 ma = (struct ma_info *)cbuserdata;
938 if (ma->is_ma == 0) {
939 cprintf("suff=%s|%s\n", partnum, cbtype);
941 if (!strcasecmp(cbtype, "multipart/alternative")) {
947 // Callback function for mime parser that opens a section for downloading
948 // we use serv_files function here:
949 extern void OpenCmdResult(char *filename, const char *mime_type);
950 void mime_download(char *name, char *filename, char *partnum, char *disp,
951 void *content, char *cbtype, char *cbcharset, size_t length,
952 char *encoding, char *cbid, void *cbuserdata)
956 // Silently go away if there's already a download open.
957 if (CC->download_fp != NULL)
961 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
962 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
964 CC->download_fp = tmpfile();
965 if (CC->download_fp == NULL) {
966 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
967 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
971 rv = fwrite(content, length, 1, CC->download_fp);
973 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
974 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
975 fclose(CC->download_fp);
976 CC->download_fp = NULL;
979 fflush(CC->download_fp);
980 rewind(CC->download_fp);
982 OpenCmdResult(filename, cbtype);
987 // Callback function for mime parser that outputs a section all at once.
988 // We can specify the desired section by part number *or* content-id.
989 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
990 void *content, char *cbtype, char *cbcharset, size_t length,
991 char *encoding, char *cbid, void *cbuserdata)
993 int *found_it = (int *)cbuserdata;
996 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
997 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1000 cprintf("%d %d|-1|%s|%s|%s\n",
1007 client_write(content, length);
1012 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
1013 struct CtdlMessage *ret = NULL;
1015 const char *upper_bound;
1017 cit_uint8_t field_header;
1021 upper_bound = Buffer + Length;
1026 // Parse the three bytes that begin EVERY message on disk.
1027 // The first is always 0xFF, the on-disk magic number.
1028 // The second is the anonymous/public type byte.
1029 // The third is the format type byte (vari, fixed, or MIME).
1033 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1036 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1037 memset(ret, 0, sizeof(struct CtdlMessage));
1039 ret->cm_magic = CTDLMESSAGE_MAGIC;
1040 ret->cm_anon_type = *mptr++; // Anon type byte
1041 ret->cm_format_type = *mptr++; // Format type byte
1043 // The rest is zero or more arbitrary fields. Load them in.
1044 // We're done when we encounter either a zero-length field or
1045 // have just processed the 'M' (message text) field.
1048 field_header = '\0';
1051 while (field_header == '\0') { // work around possibly buggy messages
1052 if (mptr >= upper_bound) {
1055 field_header = *mptr++;
1057 if (mptr >= upper_bound) {
1060 which = field_header;
1063 CM_SetField(ret, which, mptr);
1065 mptr += len + 1; // advance to next field
1067 } while ((mptr < upper_bound) && (field_header != 'M'));
1072 // Load a message from disk into memory.
1073 // This is used by CtdlOutputMsg() and other fetch functions.
1075 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1076 // using the CM_Free(); function.
1078 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1079 struct cdbdata dmsgtext;
1080 struct CtdlMessage *ret = NULL;
1082 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1083 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1084 if (dmsgtext.ptr == NULL) {
1085 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1089 if (dmsgtext.ptr[dmsgtext.len - 1] != '\0') {
1090 // FIXME LMDB cannot write to immutable memory
1091 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1092 dmsgtext.ptr[dmsgtext.len - 1] = '\0';
1095 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext.ptr, dmsgtext.len);
1100 // Always make sure there's something in the msg text field. If
1101 // it's NULL, the message text is most likely stored separately,
1102 // so go ahead and fetch that. Failing that, just set a dummy
1103 // body so other code doesn't barf.
1105 if ( (CM_IsEmpty(ret, eMessageText)) && (with_body) ) {
1106 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1107 if (dmsgtext.ptr != NULL) {
1108 CM_SetField(ret, eMessageText, dmsgtext.ptr);
1111 if (CM_IsEmpty(ret, eMessageText)) {
1112 CM_SetField(ret, eMessageText, "\r\n\r\n (no text)\r\n");
1119 // Pre callback function for multipart/alternative
1121 // NOTE: this differs from the standard behavior for a reason. Normally when
1122 // displaying multipart/alternative you want to show the _last_ usable
1123 // format in the message. Here we show the _first_ one, because it's
1124 // usually text/plain. Since this set of functions is designed for text
1125 // output to non-MIME-aware clients, this is the desired behavior.
1127 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1128 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1129 char *cbid, void *cbuserdata)
1133 ma = (struct ma_info *)cbuserdata;
1134 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1135 if (!strcasecmp(cbtype, "multipart/alternative")) {
1139 if (!strcasecmp(cbtype, "message/rfc822")) {
1146 // Post callback function for multipart/alternative
1148 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1149 void *content, char *cbtype, char *cbcharset, size_t length,
1150 char *encoding, char *cbid, void *cbuserdata)
1154 ma = (struct ma_info *)cbuserdata;
1155 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1156 if (!strcasecmp(cbtype, "multipart/alternative")) {
1160 if (!strcasecmp(cbtype, "message/rfc822")) {
1166 // Inline callback function for mime parser that wants to display text
1167 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1168 void *content, char *cbtype, char *cbcharset, size_t length,
1169 char *encoding, char *cbid, void *cbuserdata)
1176 ma = (struct ma_info *)cbuserdata;
1179 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1180 partnum, filename, cbtype, (long)length
1183 // If we're in the middle of a multipart/alternative scope and
1184 // we've already printed another section, skip this one.
1185 if ( (ma->is_ma) && (ma->did_print) ) {
1186 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1191 if ( (!strcasecmp(cbtype, "text/plain"))
1192 || (IsEmptyStr(cbtype)) ) {
1195 client_write(wptr, length);
1196 if (wptr[length-1] != '\n') {
1203 if (!strcasecmp(cbtype, "text/html")) {
1204 ptr = html_to_ascii(content, length, 80, 0);
1206 client_write(ptr, wlen);
1207 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1214 if (ma->use_fo_hooks) {
1215 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1220 if (strncasecmp(cbtype, "multipart/", 10)) {
1221 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1222 partnum, filename, cbtype, (long)length);
1228 // The client is elegant and sophisticated and wants to be choosy about
1229 // MIME content types, so figure out which multipart/alternative part
1230 // we're going to send.
1232 // We use a system of weights. When we find a part that matches one of the
1233 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1234 // and then set ma->chosen_pref to that MIME type's position in our preference
1235 // list. If we then hit another match, we only replace the first match if
1236 // the preference value is lower.
1237 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1238 void *content, char *cbtype, char *cbcharset, size_t length,
1239 char *encoding, char *cbid, void *cbuserdata)
1245 ma = (struct ma_info *)cbuserdata;
1247 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1248 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1249 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1250 if (i < ma->chosen_pref) {
1251 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1252 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1253 ma->chosen_pref = i;
1260 // Now that we've chosen our preferred part, output it.
1261 void output_preferred(char *name,
1275 int add_newline = 0;
1278 char *decoded = NULL;
1279 size_t bytes_decoded;
1282 ma = (struct ma_info *)cbuserdata;
1284 // This is not the MIME part you're looking for...
1285 if (strcasecmp(partnum, ma->chosen_part)) return;
1287 // If the content-type of this part is in our preferred formats
1288 // list, we can simply output it verbatim.
1289 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1290 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1291 if (!strcasecmp(buf, cbtype)) {
1293 if (ma->dont_decode == 0)
1294 rc = mime_decode_now (content,
1300 break; // Give us the chance, maybe theres another one.
1302 if (rc == 0) text_content = (char *)content;
1304 text_content = decoded;
1305 length = bytes_decoded;
1308 if (text_content[length-1] != '\n') {
1311 cprintf("Content-type: %s", cbtype);
1312 if (!IsEmptyStr(cbcharset)) {
1313 cprintf("; charset=%s", cbcharset);
1315 cprintf("\nContent-length: %d\n",
1316 (int)(length + add_newline) );
1317 if (!IsEmptyStr(encoding)) {
1318 cprintf("Content-transfer-encoding: %s\n", encoding);
1321 cprintf("Content-transfer-encoding: 7bit\n");
1323 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1325 if (client_write(text_content, length) == -1)
1327 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1330 if (add_newline) cprintf("\n");
1331 if (decoded != NULL) free(decoded);
1336 // No translations required or possible: output as text/plain
1337 cprintf("Content-type: text/plain\n\n");
1339 if (ma->dont_decode == 0)
1340 rc = mime_decode_now (content,
1346 return; // Give us the chance, maybe theres another one.
1348 if (rc == 0) text_content = (char *)content;
1350 text_content = decoded;
1351 length = bytes_decoded;
1354 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1355 if (decoded != NULL) free(decoded);
1360 char desired_section[64];
1366 // Callback function
1367 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1368 void *content, char *cbtype, char *cbcharset, size_t length,
1369 char *encoding, char *cbid, void *cbuserdata)
1371 struct encapmsg *encap;
1373 encap = (struct encapmsg *)cbuserdata;
1375 // Only proceed if this is the desired section...
1376 if (!strcasecmp(encap->desired_section, partnum)) {
1377 encap->msglen = length;
1378 encap->msg = malloc(length + 2);
1379 memcpy(encap->msg, content, length);
1385 // Determine whether the specified message exists in the cached_msglist
1386 // (This is a security check)
1387 int check_cached_msglist(long msgnum) {
1389 // cases in which we skip the check
1390 if (!CC) return om_ok; // not a session
1391 if (CC->client_socket <= 0) return om_ok; // not a client session
1392 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1393 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1395 // Do a binary search within the cached_msglist for the requested msgnum
1397 int max = (CC->cached_num_msgs - 1);
1399 while (max >= min) {
1400 int middle = min + (max-min) / 2 ;
1401 if (msgnum == CC->cached_msglist[middle]) {
1404 if (msgnum > CC->cached_msglist[middle]) {
1412 return om_access_denied;
1416 // Get a message off disk. (returns om_* values found in msgbase.h)
1417 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1418 int mode, // how would you like that message?
1419 int headers_only, // If nonzero, skip the message body. Also avoids loading it, if it's stored separately.
1420 int do_proto, // do Citadel protocol responses?
1421 int crlf, // If nonzero, terminate lines with CRLF instead of just LF
1422 char *section, // NULL or a message/rfc822 section
1423 int flags, // various flags; see msgbase.h
1424 char **Author, // If non-NULL, allocate a string buffer and populate the display name (caller must free)
1425 char **Address, // If non-NULL, allocate a string buffer and populate the email address (caller must free)
1426 char **MessageID // If non-NULL, allocate a string buffer and populate the message ID (caller must free)
1428 struct CtdlMessage *TheMessage = NULL;
1429 int retcode = CIT_OK;
1430 struct encapmsg encap;
1433 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1435 (section ? section : "<>")
1438 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1441 if (r == om_not_logged_in) {
1442 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1445 cprintf("%d An unknown error has occurred.\n", ERROR);
1451 // Check to make sure the message is actually IN this room
1452 r = check_cached_msglist(msg_num);
1453 if (r == om_access_denied) {
1454 // Not in the cache? We get ONE shot to check it again.
1455 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1456 r = check_cached_msglist(msg_num);
1459 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s", msg_num, CC->room.QRname);
1461 if (r == om_access_denied) {
1462 cprintf("%d message %ld was not found in this room\n", ERROR + HIGHER_ACCESS_REQUIRED, msg_num);
1468 // Fetch the message from disk. If we're in HEADERS_FAST mode, request that we don't even bother loading the body into memory.
1469 if (headers_only == HEADERS_FAST) {
1470 TheMessage = CtdlFetchMessage(msg_num, 0);
1473 TheMessage = CtdlFetchMessage(msg_num, 1);
1476 if (TheMessage == NULL) {
1477 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n", ERROR + MESSAGE_NOT_FOUND, msg_num);
1478 return(om_no_such_msg);
1481 // Here is the weird form of this command, to process only an encapsulated message/rfc822 section.
1482 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1483 memset(&encap, 0, sizeof encap);
1484 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1485 mime_parser(CM_RANGE(TheMessage, eMessageText),
1486 *extract_encapsulated_message,
1487 NULL, NULL, (void *)&encap, 0
1490 if ((Author != NULL) && (*Author == NULL)) {
1492 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1494 if ((Address != NULL) && (*Address == NULL)) {
1496 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1498 if ((MessageID != NULL) && (*MessageID == NULL)) {
1500 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1502 CM_Free(TheMessage);
1506 encap.msg[encap.msglen] = 0;
1507 TheMessage = convert_internet_message(encap.msg);
1508 encap.msg = NULL; // no free() here, TheMessage owns it now
1510 // Now we let it fall through to the bottom of this function, because TheMessage now contains the
1511 // encapsulated message instead of the top-level message. Isn't that neat?
1515 cprintf("%d msg %ld has no part %s\n", ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1517 retcode = om_no_such_msg;
1522 // Ok, output the message now
1523 if (retcode == CIT_OK) {
1524 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1526 if ((Author != NULL) && (*Author == NULL)) {
1528 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1530 if ((Address != NULL) && (*Address == NULL)) {
1532 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1534 if ((MessageID != NULL) && (*MessageID == NULL)) {
1536 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1539 CM_Free(TheMessage);
1545 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1548 char display_name[256];
1550 // begin header processing loop for Citadel message format
1551 safestrncpy(display_name, "<unknown>", sizeof display_name);
1552 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1553 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1554 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1555 safestrncpy(display_name, "****", sizeof display_name);
1557 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1558 safestrncpy(display_name, "anonymous", sizeof display_name);
1561 safestrncpy(display_name, buf, sizeof display_name);
1563 if ( (is_room_aide())
1564 && ( (TheMessage->cm_anon_type == MES_ANONONLY)
1565 || (TheMessage->cm_anon_type == MES_ANONOPT)
1568 size_t tmp = strlen(display_name);
1569 snprintf(&display_name[tmp], sizeof display_name - tmp, " [%s]", buf);
1573 // Now spew the header fields in the order we like them.
1574 for (i=0; i< NDiskFields; ++i) {
1576 Field = FieldOrder[i];
1577 if (Field != eMessageText) {
1578 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1579 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1580 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1582 if (Field == eAuthor) {
1584 cprintf("%s=%s\n", msgkeys[Field], display_name);
1587 // Masquerade display name if needed
1590 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1593 // Give the client a hint about whether the message originated locally
1594 if (Field == erFc822Addr) {
1595 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1596 cprintf("locl=yes\n"); // message originated locally.
1608 void OutputRFC822MsgHeaders(
1609 struct CtdlMessage *TheMessage,
1610 int flags, // should the message be exported clean
1611 const char *nl, int nlen,
1612 char *mid, long sizeof_mid,
1613 char *suser, long sizeof_suser,
1614 char *luser, long sizeof_luser,
1615 char *fuser, long sizeof_fuser,
1616 char *snode, long sizeof_snode)
1618 char datestamp[100];
1619 int subject_found = 0;
1626 for (i = 0; i < NDiskFields; ++i) {
1627 if (TheMessage->cm_fields[FieldOrder[i]]) {
1628 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1629 switch (FieldOrder[i]) {
1631 safestrncpy(luser, mptr, sizeof_luser);
1632 safestrncpy(suser, mptr, sizeof_suser);
1635 if ((flags & QP_EADDR) != 0) {
1636 mptr = qp_encode_email_addrs(mptr);
1638 sanitize_truncated_recipient(mptr);
1639 cprintf("CC: %s%s", mptr, nl);
1642 cprintf("Return-Path: %s%s", mptr, nl);
1645 cprintf("List-ID: %s%s", mptr, nl);
1648 if ((flags & QP_EADDR) != 0)
1649 mptr = qp_encode_email_addrs(mptr);
1651 while ((*hptr != '\0') && isspace(*hptr))
1653 if (!IsEmptyStr(hptr))
1654 cprintf("Envelope-To: %s%s", hptr, nl);
1657 cprintf("Subject: %s%s", mptr, nl);
1661 safestrncpy(mid, mptr, sizeof_mid);
1664 safestrncpy(fuser, mptr, sizeof_fuser);
1667 if (haschar(mptr, '@') == 0) {
1668 sanitize_truncated_recipient(mptr);
1669 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1673 if ((flags & QP_EADDR) != 0) {
1674 mptr = qp_encode_email_addrs(mptr);
1676 sanitize_truncated_recipient(mptr);
1677 cprintf("To: %s", mptr);
1682 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1683 cprintf("Date: %s%s", datestamp, nl);
1686 cprintf("References: ");
1687 k = num_tokens(mptr, '|');
1688 for (j=0; j<k; ++j) {
1689 extract_token(buf, mptr, j, '|', sizeof buf);
1690 cprintf("<%s>", buf);
1701 while ((*hptr != '\0') && isspace(*hptr))
1703 if (!IsEmptyStr(hptr))
1704 cprintf("Reply-To: %s%s", mptr, nl);
1716 // these don't map to mime message headers.
1719 if (mptr != mpptr) {
1724 if (subject_found == 0) {
1725 cprintf("Subject: (no subject)%s", nl);
1730 void Dump_RFC822HeadersBody(
1731 struct CtdlMessage *TheMessage,
1732 int headers_only, // eschew the message body?
1733 int flags, // should the bessage be exported clean?
1734 const char *nl, int nlen)
1736 cit_uint8_t prev_ch;
1738 const char *StartOfText = StrBufNOTNULL;
1741 int nllen = strlen(nl);
1745 mptr = TheMessage->cm_fields[eMessageText];
1748 while (*mptr != '\0') {
1749 if (*mptr == '\r') {
1753 if ((!eoh) && (*mptr == '\n')) {
1754 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1756 eoh = *(mptr+1) == '\n';
1760 StartOfText = strchr(StartOfText, '\n');
1761 StartOfText = strchr(StartOfText, '\n');
1764 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1765 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1766 ((headers_only != HEADERS_NONE) &&
1767 (headers_only != HEADERS_ONLY))
1769 if (*mptr == '\n') {
1770 memcpy(&outbuf[outlen], nl, nllen);
1772 outbuf[outlen] = '\0';
1775 outbuf[outlen++] = *mptr;
1779 if (flags & ESC_DOT) {
1780 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1781 outbuf[outlen++] = '.';
1786 if (outlen > 1000) {
1787 if (client_write(outbuf, outlen) == -1) {
1788 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1791 lfSent = (outbuf[outlen - 1] == '\n');
1796 client_write(outbuf, outlen);
1797 lfSent = (outbuf[outlen - 1] == '\n');
1800 client_write(nl, nlen);
1804 // If the format type on disk is 1 (fixed-format), then we want
1805 // everything to be output completely literally ... regardless of
1806 // what message transfer format is in use.
1807 void DumpFormatFixed(
1808 struct CtdlMessage *TheMessage,
1809 int mode, // how would you like that message?
1810 const char *nl, int nllen)
1818 mptr = TheMessage->cm_fields[eMessageText];
1820 if (mode == MT_MIME) {
1821 cprintf("Content-type: text/plain\n\n");
1825 while (ch = *mptr++, ch > 0) {
1829 if ((buflen > 250) && (!xlline)){
1833 while ((buflen > 0) &&
1834 (!isspace(buf[buflen])))
1840 mptr -= tbuflen - buflen;
1846 // if we reach the outer bounds of our buffer, abort without respect for what we purge.
1847 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1852 memcpy (&buf[buflen], nl, nllen);
1856 if (client_write(buf, buflen) == -1) {
1857 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1869 if (!IsEmptyStr(buf)) {
1870 cprintf("%s%s", buf, nl);
1875 // Get a message off disk. (returns om_* values found in msgbase.h)
1876 int CtdlOutputPreLoadedMsg(
1877 struct CtdlMessage *TheMessage,
1878 int mode, // how would you like that message?
1879 int headers_only, // eschew the message body?
1880 int do_proto, // do Citadel protocol responses?
1881 int crlf, // Use CRLF newlines instead of LF?
1882 int flags // should the bessage be exported clean?
1885 const char *nl; // newline string
1889 // Buffers needed for RFC822 translation. These are all filled
1890 // using functions that are bounds-checked, and therefore we can
1891 // make them substantially smaller than SIZ.
1898 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1899 ((TheMessage == NULL) ? "NULL" : "not null"),
1900 mode, headers_only, do_proto, crlf
1903 strcpy(mid, "unknown");
1904 nl = (crlf ? "\r\n" : "\n");
1905 nlen = crlf ? 2 : 1;
1907 if (!CM_IsValidMsg(TheMessage)) {
1908 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1909 return(om_no_such_msg);
1912 // Suppress envelope recipients if required to avoid disclosing BCC addresses.
1913 // Pad it with spaces in order to avoid changing the RFC822 length of the message.
1914 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1915 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1918 // Are we downloading a MIME component?
1919 if (mode == MT_DOWNLOAD) {
1920 if (TheMessage->cm_format_type != FMT_RFC822) {
1922 cprintf("%d This is not a MIME message.\n", ERROR + ILLEGAL_VALUE);
1925 else if (CC->download_fp != NULL) {
1927 cprintf( "%d You already have a download open.\n", ERROR + RESOURCE_BUSY);
1931 // Parse the message text component
1932 mime_parser(CM_RANGE(TheMessage, eMessageText), *mime_download, NULL, NULL, NULL, 0);
1934 // If there's no file open by this time, the requested * section wasn't found, so print an error
1935 if (CC->download_fp == NULL) {
1937 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
1941 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1944 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1945 // in a single server operation instead of opening a download file.
1946 if (mode == MT_SPEW_SECTION) {
1947 if (TheMessage->cm_format_type != FMT_RFC822) {
1949 cprintf("%d This is not a MIME message.\n",
1950 ERROR + ILLEGAL_VALUE);
1953 // Locate and parse the component specified by the caller
1955 mime_parser(CM_RANGE(TheMessage, eMessageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1957 // If section wasn't found, print an error
1960 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
1964 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1967 // now for the user-mode message reading loops
1968 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1970 // Does the caller want to skip the headers?
1971 if (headers_only == HEADERS_NONE) goto START_TEXT;
1973 // Tell the client which format type we're using.
1974 if ( (mode == MT_CITADEL) && (do_proto) ) {
1975 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
1978 // nhdr=yes means that we're only displaying headers, no body
1979 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1980 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1983 cprintf("nhdr=yes\n");
1986 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1987 OutputCtdlMsgHeaders(TheMessage, do_proto);
1990 // begin header processing loop for RFC822 transfer format
1995 if (mode == MT_RFC822) {
1996 OutputRFC822MsgHeaders(
2001 suser, sizeof(suser),
2002 luser, sizeof(luser),
2003 fuser, sizeof(fuser),
2004 snode, sizeof(snode)
2008 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2009 suser[i] = tolower(suser[i]);
2010 if (!isalnum(suser[i])) suser[i]='_';
2013 if (mode == MT_RFC822) {
2014 // Make the message ID RFC2822 compliant
2015 cprintf("Message-ID: <%s%s%s>%s", // put it in angle brackets
2017 (strchr(mid, '@') ? "" : "@"), // if there is no domain part,
2018 (strchr(mid, '@') ? "" : snode), // tack on ours.
2022 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2023 cprintf("From: \"----\" <x@x.org>%s", nl);
2025 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2026 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2028 else if (!IsEmptyStr(fuser)) {
2029 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2032 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2035 // Blank line signifying RFC822 end-of-headers
2036 if (TheMessage->cm_format_type != FMT_RFC822) {
2041 // end header processing loop ... at this point, we're in the text
2043 if (headers_only == HEADERS_FAST) goto DONE;
2045 // Tell the client about the MIME parts in this message
2046 if (TheMessage->cm_format_type == FMT_RFC822) {
2047 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2048 memset(&ma, 0, sizeof(struct ma_info));
2049 mime_parser(CM_RANGE(TheMessage, eMessageText),
2050 (do_proto ? *list_this_part : NULL),
2051 (do_proto ? *list_this_pref : NULL),
2052 (do_proto ? *list_this_suff : NULL),
2055 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2056 Dump_RFC822HeadersBody(TheMessage, headers_only, flags, nl, nlen);
2061 if (headers_only == HEADERS_ONLY) {
2065 // signify start of msg text
2066 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2067 if (do_proto) cprintf("text\n");
2070 if (TheMessage->cm_format_type == FMT_FIXED) {
2071 DumpFormatFixed( TheMessage, mode, nl, nlen);
2074 // If the message on disk is format 0 (Citadel vari-format), we
2075 // output using the formatter at 80 columns. This is the final output
2076 // form if the transfer format is RFC822, but if the transfer format
2077 // is Citadel proprietary, it'll still work, because the indentation
2078 // for new paragraphs is correct and the client will reformat the
2079 // message to the reader's screen width.
2081 if (TheMessage->cm_format_type == FMT_CITADEL) {
2082 if (mode == MT_MIME) {
2083 cprintf("Content-type: text/x-citadel-variformat\n\n");
2085 memfmout(TheMessage->cm_fields[eMessageText], nl);
2088 // If the message on disk is format 4 (MIME), we've gotta hand it
2089 // off to the MIME parser. The client has already been told that
2090 // this message is format 1 (fixed format), so the callback function
2091 // we use will display those parts as-is.
2093 if (TheMessage->cm_format_type == FMT_RFC822) {
2094 memset(&ma, 0, sizeof(struct ma_info));
2096 if (mode == MT_MIME) {
2097 ma.use_fo_hooks = 0;
2098 strcpy(ma.chosen_part, "1");
2099 ma.chosen_pref = 9999;
2100 ma.dont_decode = CC->msg4_dont_decode;
2101 mime_parser(CM_RANGE(TheMessage, eMessageText),
2102 *choose_preferred, *fixed_output_pre,
2103 *fixed_output_post, (void *)&ma, 1);
2104 mime_parser(CM_RANGE(TheMessage, eMessageText),
2105 *output_preferred, NULL, NULL, (void *)&ma, 1);
2108 ma.use_fo_hooks = 1;
2109 mime_parser(CM_RANGE(TheMessage, eMessageText),
2110 *fixed_output, *fixed_output_pre,
2111 *fixed_output_post, (void *)&ma, 0);
2116 DONE: // now we're done
2117 if (do_proto) cprintf("000\n");
2122 // Save one or more message pointers into a specified room
2123 // (Returns 0 for success, nonzero for failure)
2124 // roomname may be NULL to use the current room
2126 // Note that the 'msg_in' field may be set to NULL, in which case
2127 // the message will be fetched from disk, by number, if we need to perform
2128 // replication checks. This adds an additional database read, so if the
2129 // caller already has the message in memory then it should be supplied. (Obviously
2130 // this mode of operation only works if we're saving a single message.)
2132 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2133 int do_repl_check, struct CtdlMessage *msg_in, int suppress_refcount_adj
2136 char hold_rm[ROOMNAMELEN];
2137 struct cdbdata *cdbfr;
2140 long highest_msg = 0L;
2143 struct CtdlMessage *msg = NULL;
2145 long *msgs_to_be_merged = NULL;
2146 int num_msgs_to_be_merged = 0;
2149 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2150 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2153 strcpy(hold_rm, CC->room.QRname);
2156 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2157 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2158 if (num_newmsgs > 1) msg_in = NULL;
2160 // Now the regular stuff
2161 if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
2162 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2163 return(ERROR + ROOM_NOT_FOUND);
2166 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2167 num_msgs_to_be_merged = 0;
2168 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2170 // Create a list of msgid's which were supplied by the caller, but do
2171 // not already exist in the target room. It is absolutely taboo to
2172 // have more than one reference to the same message in a room.
2173 for (i=0; i<num_newmsgs; ++i) {
2175 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2176 if (msglist[j] == newmsgidlist[i]) {
2181 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2185 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2187 // Now merge the new messages
2188 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2189 if (msglist == NULL) {
2190 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2191 free(msgs_to_be_merged);
2193 return (ERROR + INTERNAL_ERROR);
2195 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2196 num_msgs += num_msgs_to_be_merged;
2198 // Sort the message list, so all the msgid's are in order
2199 num_msgs = sort_msglist(msglist, num_msgs);
2201 // Determine the highest message number
2202 highest_msg = msglist[num_msgs - 1];
2204 // Write it back to disk.
2205 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2207 // Free up the memory we used.
2210 // Update the highest-message pointer and unlock the room.
2211 CC->room.QRhighest = highest_msg;
2212 CtdlPutRoomLock(&CC->room);
2214 // Perform replication checks if necessary
2215 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2216 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2218 for (i=0; i<num_msgs_to_be_merged; ++i) {
2219 msgid = msgs_to_be_merged[i];
2221 if (msg_in != NULL) {
2225 msg = CtdlFetchMessage(msgid, 0);
2229 ReplicationChecks(msg);
2231 // If the message has an Exclusive ID, index that...
2232 if (!CM_IsEmpty(msg, eExclusiveID)) {
2233 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2236 // Free up the memory we may have allocated
2237 if (msg != msg_in) {
2246 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2249 // Submit this room for processing by hooks
2250 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2251 if (total_roomhook_errors) {
2252 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2255 // Go back to the room we were in before we wandered here...
2256 CtdlGetRoom(&CC->room, hold_rm);
2258 // Bump the reference count for all messages which were merged
2259 if (!suppress_refcount_adj) {
2260 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2263 // Free up memory...
2264 if (msgs_to_be_merged != NULL) {
2265 free(msgs_to_be_merged);
2273 // This is the same as CtdlSaveMsgPointersInRoom() but it only accepts a single message.
2274 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg_in) {
2275 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, msg_in, 0);
2279 // Message base operation to save a new message to the message store
2280 // (returns new message number)
2282 // This is the back end for CtdlSubmitMsg() and should not be directly
2283 // called by server-side modules.
2284 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid) {
2285 long error_count = 0;
2287 // Serialize our data structure for storage in the database
2288 struct ser_ret smr = CtdlSerializeMessage(msg);
2291 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2295 // STORAGE STRATEGY:
2296 // * If headers+content fit are <= 4K, store them together. It's likely to be one
2297 // memory page, one disk sector, etc. so we benefit from a single disk operation.
2298 // * If headers+content exceed 4K, store them separately so we don't end up fetching
2299 // many gigamegs of data when we're just scanning the headers.
2300 // * We are using a threshold of 4000 bytes so that there's some room for overhead
2301 // if the database or OS adds any metadata to that one disk page.
2303 if (smr.len <= 4000) { // all together less than one page, store them together
2305 if (cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len)) {
2311 else { // exceed one page, store headers in MSGMAIN, body in BIGMSGS
2313 if (cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, (smr.msgstart - smr.ser))) {
2317 if (cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), smr.msgstart+1, (smr.len - (smr.msgstart - smr.ser) - 1) )) {
2323 if (error_count > 0) {
2324 syslog(LOG_ERR, "msgbase: encountered %ld errors storing message %ld", error_count, msgid);
2327 // Free the memory we used for the serialized message
2329 return(error_count);
2333 long send_message(struct CtdlMessage *msg) {
2339 // Get a new message number
2340 newmsgid = get_new_message_number();
2342 // Generate an ID if we don't have one already
2343 if (CM_IsEmpty(msg, emessageId)) {
2344 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%lX-%lX@%s",
2345 (long unsigned int) time(NULL),
2346 (long unsigned int) newmsgid,
2347 CtdlGetConfigStr("c_fqdn")
2349 CM_SetField(msg, emessageId, msgidbuf);
2352 retval = CtdlSaveThisMessage(msg, newmsgid);
2358 // Return the *local* message ID to the caller (even if we're storing a remotely originated message)
2363 // Serialize a struct CtdlMessage into the format used on disk.
2365 // This function returns a "struct ser_ret" (defined in server.h) which
2366 // contains the length of the serialized message and a pointer to the
2367 // serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2368 struct ser_ret CtdlSerializeMessage(struct CtdlMessage *msg) {
2375 ret.msgstart = NULL;
2377 // Check for valid message format
2378 if (CM_IsValidMsg(msg) == 0) {
2379 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2384 assert(FieldOrder[NDiskFields-1] == eMessageText); // Message text MUST be last!
2385 for (i=0; i < NDiskFields; ++i) {
2386 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2387 ret.len += msg->cm_lengths[FieldOrder[i]] + 2;
2391 ret.ser = malloc(ret.len);
2392 if (ret.ser == NULL) {
2393 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret.len);
2396 ret.msgstart = NULL;
2401 ret.ser[1] = msg->cm_anon_type;
2402 ret.ser[2] = msg->cm_format_type;
2405 for (i=0; i < NDiskFields; ++i) {
2406 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2407 if (FieldOrder[i] == eMessageText) {
2408 ret.msgstart = &ret.ser[wlen]; // Make a note where the message text begins
2410 ret.ser[wlen++] = (char)FieldOrder[i];
2411 memcpy(&ret.ser[wlen], msg->cm_fields[FieldOrder[i]], msg->cm_lengths[FieldOrder[i]] + 1);
2412 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2416 assert(ret.len == wlen); // Make sure we measured it correctly
2421 // Check to see if any messages already exist in the current room which
2422 // carry the same Exclusive ID as this one. If any are found, delete them.
2423 void ReplicationChecks(struct CtdlMessage *msg) {
2424 long old_msgnum = (-1L);
2426 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2428 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2430 // No exclusive id? Don't do anything.
2431 if (msg == NULL) return;
2432 if (CM_IsEmpty(msg, eExclusiveID)) return;
2434 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2435 if (old_msgnum > 0L) {
2436 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2437 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2442 // Save a message to disk and submit it into the delivery system.
2443 long CtdlSubmitMsg(struct CtdlMessage *msg, // message to save
2444 struct recptypes *recps, // recipients (if mail)
2445 const char *force // force a particular room?
2447 char hold_rm[ROOMNAMELEN];
2448 char actual_rm[ROOMNAMELEN];
2449 char force_room[ROOMNAMELEN];
2450 char content_type[SIZ]; // We have to learn this
2451 char recipient[SIZ];
2452 char bounce_to[1024];
2455 const char *mptr = NULL;
2456 struct ctdluser userbuf;
2458 struct MetaData smi;
2459 char *collected_addresses = NULL;
2460 struct addresses_to_be_filed *aptr = NULL;
2461 StrBuf *saved_rfc822_version = NULL;
2462 int qualified_for_journaling = 0;
2464 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2465 if (CM_IsValidMsg(msg) == 0) return(-1); // self check
2467 // If this message has no timestamp, we take the liberty of giving it one, right now.
2468 if (CM_IsEmpty(msg, eTimestamp)) {
2469 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2472 // If this message has no path, we generate one.
2473 if (CM_IsEmpty(msg, eMessagePath)) {
2474 if (!CM_IsEmpty(msg, eAuthor)) {
2475 CM_CopyField(msg, eMessagePath, eAuthor);
2476 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2477 if (isspace(msg->cm_fields[eMessagePath][a])) {
2478 msg->cm_fields[eMessagePath][a] = ' ';
2483 CM_SetField(msg, eMessagePath, "unknown");
2487 if (force == NULL) {
2488 force_room[0] = '\0';
2491 strcpy(force_room, force);
2494 // Learn about what's inside, because it's what's inside that counts
2495 if (CM_IsEmpty(msg, eMessageText)) {
2496 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2500 switch (msg->cm_format_type) {
2502 strcpy(content_type, "text/x-citadel-variformat");
2505 strcpy(content_type, "text/plain");
2508 strcpy(content_type, "text/plain");
2509 mptr = bmstrcasestr(msg->cm_fields[eMessageText], "Content-type:");
2512 safestrncpy(content_type, &mptr[13], sizeof content_type);
2513 string_trim(content_type);
2514 aptr = content_type;
2515 while (!IsEmptyStr(aptr)) {
2530 // Goto the correct room
2531 room = (recps) ? CC->room.QRname : SENTITEMS;
2532 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2533 strcpy(hold_rm, CC->room.QRname);
2534 strcpy(actual_rm, CC->room.QRname);
2535 if (recps != NULL) {
2536 strcpy(actual_rm, SENTITEMS);
2539 // If the user is a twit, move to the twit room for posting
2541 if (CC->user.axlevel == AxProbU) {
2542 strcpy(hold_rm, actual_rm);
2543 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2544 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2548 // ...or if this message is destined for Aide> then go there.
2549 if (!IsEmptyStr(force_room)) {
2550 strcpy(actual_rm, force_room);
2553 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2554 if (strcasecmp(actual_rm, CC->room.QRname)) {
2555 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2558 // If this message has no O (room) field, generate one.
2559 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2560 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2563 // Perform "before save" hooks (aborting if any return nonzero)
2564 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2565 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2567 // If this message has an Exclusive ID, and the room is replication
2568 // checking enabled, then do replication checks.
2569 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2570 ReplicationChecks(msg);
2574 syslog(LOG_DEBUG, "msgbase: saving to disk");
2575 newmsgid = send_message(msg);
2576 if (newmsgid <= 0L) return(-5);
2578 // Write a supplemental message info record. This doesn't have to be
2579 // a critical section because nobody else knows about this message yet.
2580 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2581 memset(&smi, 0, sizeof(struct MetaData));
2582 smi.meta_msgnum = newmsgid;
2583 smi.meta_refcount = 0;
2584 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2586 // Measure how big this message will be when rendered as RFC822.
2587 // We do this for two reasons:
2588 // 1. We need the RFC822 length for the new metadata record, so the
2589 // POP and IMAP services don't have to calculate message lengths
2590 // while the user is waiting (multiplied by potentially hundreds
2591 // or thousands of messages).
2592 // 2. If journaling is enabled, we will need an RFC822 version of the
2593 // message to attach to the journalized copy.
2594 if (CC->redirect_buffer != NULL) {
2595 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2596 exit(CTDLEXIT_REDIRECT);
2598 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2599 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2600 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2601 saved_rfc822_version = CC->redirect_buffer;
2602 CC->redirect_buffer = NULL;
2606 // Now figure out where to store the pointers
2607 syslog(LOG_DEBUG, "msgbase: storing pointers");
2609 // If this is being done by the networker delivering a private
2610 // message, we want to BYPASS saving the sender's copy (because there
2611 // is no local sender; it would otherwise go to the Trashcan).
2612 if ((!CC->internal_pgm) || (recps == NULL)) {
2613 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2614 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2615 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2619 // For internet mail, drop a copy in the outbound queue room
2620 if ((recps != NULL) && (recps->num_internet > 0)) {
2621 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2624 // If other rooms are specified, drop them there too.
2625 if ((recps != NULL) && (recps->num_room > 0)) {
2626 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2627 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2628 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2629 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2633 // Decide where bounces need to be delivered
2634 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2635 if (CC->logged_in) {
2636 strcpy(bounce_to, CC->user.fullname);
2638 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2639 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2641 recps->bounce_to = bounce_to;
2644 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2646 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2647 if ((recps != NULL) && (recps->num_local > 0)) {
2651 pch = recps->recp_local;
2652 recps->recp_local = recipient;
2653 ntokens = num_tokens(pch, '|');
2654 for (i=0; i<ntokens; ++i) {
2655 extract_token(recipient, pch, i, '|', sizeof recipient);
2656 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2657 if (CtdlGetUser(&userbuf, recipient) == 0) {
2658 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2659 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2660 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2661 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2664 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2665 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2668 recps->recp_local = pch;
2671 // Perform "after save" hooks
2672 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2674 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2675 CM_FlushField(msg, eVltMsgNum);
2677 // Go back to the room we started from
2678 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2679 if (strcasecmp(hold_rm, CC->room.QRname)) {
2680 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2683 // Any addresses to harvest for someone's address book?
2684 if ( (CC->logged_in) && (recps != NULL) ) {
2685 collected_addresses = harvest_collected_addresses(msg);
2688 if (collected_addresses != NULL) {
2689 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2690 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2691 aptr->roomname = strdup(actual_rm);
2692 aptr->collected_addresses = collected_addresses;
2693 begin_critical_section(S_ATBF);
2696 end_critical_section(S_ATBF);
2699 // Determine whether this message qualifies for journaling.
2700 if (!CM_IsEmpty(msg, eJournal)) {
2701 qualified_for_journaling = 0;
2704 if (recps == NULL) {
2705 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2707 else if (recps->num_local + recps->num_internet > 0) {
2708 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2711 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2715 // Do we have to perform journaling? If so, hand off the saved
2716 // RFC822 version will be handed off to the journaler for background
2717 // submit. Otherwise, we have to free the memory ourselves.
2718 if (saved_rfc822_version != NULL) {
2719 if (qualified_for_journaling) {
2720 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2723 FreeStrBuf(&saved_rfc822_version);
2727 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2728 recps->bounce_to = NULL;
2735 // Convenience function for generating small administrative messages.
2736 long quickie_message(char *from,
2744 struct CtdlMessage *msg;
2745 struct recptypes *recp = NULL;
2747 msg = malloc(sizeof(struct CtdlMessage));
2748 memset(msg, 0, sizeof(struct CtdlMessage));
2749 msg->cm_magic = CTDLMESSAGE_MAGIC;
2750 msg->cm_anon_type = MES_NORMAL;
2751 msg->cm_format_type = format_type;
2753 if (!IsEmptyStr(from)) {
2754 CM_SetField(msg, eAuthor, from);
2756 else if (!IsEmptyStr(fromaddr)) {
2758 CM_SetField(msg, eAuthor, fromaddr);
2759 pAt = strchr(msg->cm_fields[eAuthor], '@');
2761 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2765 msg->cm_fields[eAuthor] = strdup("Citadel");
2768 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr);
2769 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
2770 if (!IsEmptyStr(to)) {
2771 CM_SetField(msg, eRecipient, to);
2772 recp = validate_recipients(to, 0);
2774 if (!IsEmptyStr(subject)) {
2775 CM_SetField(msg, eMsgSubject, subject);
2777 if (!IsEmptyStr(text)) {
2778 CM_SetField(msg, eMessageText, text);
2781 long msgnum = CtdlSubmitMsg(msg, recp, room);
2783 if (recp != NULL) free_recipients(recp);
2788 // Back end function used by CtdlMakeMessage() and similar functions
2789 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2791 size_t maxlen, // maximum message length
2792 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2793 int crlf // CRLF newlines instead of LF
2801 LineBuf = NewStrBufPlain(NULL, SIZ);
2802 if (exist == NULL) {
2803 Message = NewStrBufPlain(NULL, 4 * SIZ);
2806 Message = NewStrBufDup(exist);
2809 // Do we need to change leading ".." to "." for SMTP escaping?
2810 if ((tlen == 1) && (*terminator == '.')) {
2814 // read in the lines of message text one by one
2816 if (CtdlClientGetLine(LineBuf) < 0) {
2819 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2822 if ( (!flushing) && (!finished) ) {
2824 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2827 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2830 // Unescape SMTP-style input of two dots at the beginning of the line
2831 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2832 StrBufCutLeft(LineBuf, 1);
2834 StrBufAppendBuf(Message, LineBuf, 0);
2837 // if we've hit the max msg length, flush the rest
2838 if (StrLength(Message) >= maxlen) {
2842 } while (!finished);
2843 FreeStrBuf(&LineBuf);
2846 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2853 // Back end function used by CtdlMakeMessage() and similar functions
2854 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2856 size_t maxlen, // maximum message length
2857 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2858 int crlf // CRLF newlines instead of LF
2862 Message = CtdlReadMessageBodyBuf(terminator,
2868 if (Message == NULL) {
2872 return SmashStrBuf(&Message);
2877 struct CtdlMessage *CtdlMakeMessage(
2878 struct ctdluser *author, // author's user structure
2879 char *recipient, // NULL if it's not mail
2880 char *recp_cc, // NULL if it's not mail
2881 char *room, // room where it's going
2882 int type, // see MES_ types in header file
2883 int format_type, // variformat, plain text, MIME...
2884 char *fake_name, // who we're masquerading as
2885 char *my_email, // which of my email addresses to use (empty is ok)
2886 char *subject, // Subject (optional)
2887 char *euid_in, // ...or NULL if this is irrelevant
2888 char *preformatted_text, // ...or NULL to read text from client
2889 char *references // Thread references
2891 return CtdlMakeMessageLen(
2892 author, // author's user structure
2893 recipient, // NULL if it's not mail
2894 (recipient)?strlen(recipient) : 0,
2895 recp_cc, // NULL if it's not mail
2896 (recp_cc)?strlen(recp_cc): 0,
2897 room, // room where it's going
2898 (room)?strlen(room): 0,
2899 type, // see MES_ types in header file
2900 format_type, // variformat, plain text, MIME...
2901 fake_name, // who we're masquerading as
2902 (fake_name)?strlen(fake_name): 0,
2903 my_email, // which of my email addresses to use (empty is ok)
2904 (my_email)?strlen(my_email): 0,
2905 subject, // Subject (optional)
2906 (subject)?strlen(subject): 0,
2907 euid_in, // ...or NULL if this is irrelevant
2908 (euid_in)?strlen(euid_in):0,
2909 preformatted_text, // ...or NULL to read text from client
2910 (preformatted_text)?strlen(preformatted_text) : 0,
2911 references, // Thread references
2912 (references)?strlen(references):0);
2917 // Build a binary message to be saved on disk.
2918 // (NOTE: if you supply 'preformatted_text', the buffer you give it
2919 // will become part of the message. This means you are no longer
2920 // responsible for managing that memory -- it will be freed along with
2921 // the rest of the fields when CM_Free() is called.)
2922 struct CtdlMessage *CtdlMakeMessageLen(
2923 struct ctdluser *author, // author's user structure
2924 char *recipient, // NULL if it's not mail
2926 char *recp_cc, // NULL if it's not mail
2928 char *room, // room where it's going
2930 int type, // see MES_ types in header file
2931 int format_type, // variformat, plain text, MIME...
2932 char *fake_name, // who we're masquerading as
2934 char *my_email, // which of my email addresses to use (empty is ok)
2936 char *subject, // Subject (optional)
2938 char *euid_in, // ...or NULL if this is irrelevant
2940 char *preformatted_text, // ...or NULL to read text from client
2942 char *references, // Thread references
2947 struct CtdlMessage *msg;
2949 StrBuf *FakeEncAuthor = NULL;
2951 msg = malloc(sizeof(struct CtdlMessage));
2952 memset(msg, 0, sizeof(struct CtdlMessage));
2953 msg->cm_magic = CTDLMESSAGE_MAGIC;
2954 msg->cm_anon_type = type;
2955 msg->cm_format_type = format_type;
2957 if (recipient != NULL) rcplen = string_trim(recipient);
2958 if (recp_cc != NULL) cclen = string_trim(recp_cc);
2960 // Path or Return-Path
2962 CM_SetField(msg, eMessagePath, my_email);
2964 else if (!IsEmptyStr(author->fullname)) {
2965 CM_SetField(msg, eMessagePath, author->fullname);
2967 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
2969 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
2970 CM_SetField(msg, eTimestamp, buf);
2973 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
2976 FakeAuthor = NewStrBufPlain (author->fullname, -1);
2978 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
2979 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
2980 FreeStrBuf(&FakeAuthor);
2982 if (!!IsEmptyStr(CC->room.QRname)) {
2983 if (CC->room.QRflags & QR_MAILBOX) { // room
2984 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11]);
2987 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2992 CM_SetField(msg, eRecipient, recipient);
2995 CM_SetField(msg, eCarbonCopY, recp_cc);
2999 CM_SetField(msg, erFc822Addr, my_email);
3001 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3002 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
3005 if (subject != NULL) {
3007 length = string_trim(subject);
3013 while ((subject[i] != '\0') && (IsAscii = isascii(subject[i]) != 0 )) {
3017 CM_SetField(msg, eMsgSubject, subject);
3019 else { // ok, we've got utf8 in the string.
3021 rfc2047Subj = rfc2047encode(subject, length);
3022 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3029 CM_SetField(msg, eExclusiveID, euid_in);
3033 CM_SetField(msg, eWeferences, references);
3036 if (preformatted_text != NULL) {
3037 CM_SetField(msg, eMessageText, preformatted_text);
3041 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3042 if (MsgBody != NULL) {
3043 CM_SetAsFieldSB(msg, eMessageText, &MsgBody);
3051 // API function to delete messages which match a set of criteria
3052 // (returns the actual number of messages deleted)
3053 int CtdlDeleteMessages(const char *room_name, // which room
3054 long *dmsgnums, // array of msg numbers to be deleted
3055 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3056 char *content_type // or "" for any. regular expressions expected.
3058 struct ctdlroom qrbuf;
3059 struct cdbdata *cdbfr;
3060 long *msglist = NULL;
3061 long *dellist = NULL;
3064 int num_deleted = 0;
3066 struct MetaData smi;
3069 int need_to_free_re = 0;
3071 if (content_type) if (!IsEmptyStr(content_type)) {
3072 regcomp(&re, content_type, 0);
3073 need_to_free_re = 1;
3075 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3077 // get room record, obtaining a lock...
3078 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3079 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3080 if (need_to_free_re) regfree(&re);
3081 return(0); // room not found
3084 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3086 dellist = malloc(num_msgs * sizeof(long));
3087 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3088 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3089 int have_more_del = 1;
3091 num_msgs = sort_msglist(msglist, num_msgs);
3092 if (num_dmsgnums > 1) {
3093 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3097 while ((i < num_msgs) && (have_more_del)) {
3100 // Set/clear a bit for each criterion
3102 // 0 messages in the list or a null list means that we are
3103 // interested in deleting any messages which meet the other criteria.
3105 delete_this |= 0x01;
3108 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3113 if (msglist[i] == dmsgnums[j]) {
3114 delete_this |= 0x01;
3117 have_more_del = (j < num_dmsgnums);
3120 if (have_contenttype) {
3121 GetMetaData(&smi, msglist[i]);
3122 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3123 delete_this |= 0x02;
3127 delete_this |= 0x02;
3130 // Delete message only if all bits are set
3131 if (delete_this == 0x03) {
3132 dellist[num_deleted++] = msglist[i];
3138 num_msgs = sort_msglist(msglist, num_msgs);
3139 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3142 qrbuf.QRhighest = msglist[num_msgs - 1];
3145 qrbuf.QRhighest = 0;
3148 CtdlPutRoomLock(&qrbuf);
3150 // Go through the messages we pulled out of the index, and decrement
3151 // their reference counts by 1. If this is the only room the message
3152 // was in, the reference count will reach zero and the message will
3153 // automatically be deleted from the database. We do this in a
3154 // separate pass because there might be plug-in hooks getting called,
3155 // and we don't want that happening during an S_ROOMS critical section.
3157 for (i=0; i<num_deleted; ++i) {
3158 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3160 AdjRefCountList(dellist, num_deleted, -1);
3162 // Now free the memory we used, and go away.
3163 if (msglist != NULL) free(msglist);
3164 if (dellist != NULL) free(dellist);
3165 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3166 if (need_to_free_re) regfree(&re);
3167 return (num_deleted);
3171 // GetMetaData() - Get the supplementary record for a message
3172 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3173 struct cdbdata cdbsmi;
3176 memset(smibuf, 0, sizeof(struct MetaData));
3177 smibuf->meta_msgnum = msgnum;
3178 smibuf->meta_refcount = 1; // Default reference count is 1
3180 // Use the negative of the message number for its supp record index
3181 TheIndex = (0L - msgnum);
3183 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3184 if (cdbsmi.ptr == NULL) {
3185 return; // record not found; leave it alone
3187 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3192 // PutMetaData() - (re)write supplementary record for a message
3193 void PutMetaData(struct MetaData *smibuf) {
3196 // Use the negative of the message number for the metadata db index
3197 TheIndex = (0L - smibuf->meta_msgnum);
3198 cdb_store(CDB_MSGMAIN, &TheIndex, (int)sizeof(long), smibuf, (int)sizeof(struct MetaData));
3202 // Convenience function to process a big block of AdjRefCount() operations
3203 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3206 for (i = 0; i < nmsg; i++) {
3207 AdjRefCount(msgnum[i], incr);
3212 // AdjRefCount - adjust the reference count for a message.
3213 // We need to delete from disk any message whose reference count reaches zero.
3214 void AdjRefCount(long msgnum, int incr) {
3215 struct MetaData smi;
3218 // This is a *tight* critical section; please keep it that way, as
3219 // it may get called while nested in other critical sections.
3220 // Complicating this any further will surely cause deadlock!
3221 begin_critical_section(S_SUPPMSGMAIN);
3222 GetMetaData(&smi, msgnum);
3223 smi.meta_refcount += incr;
3225 end_critical_section(S_SUPPMSGMAIN);
3226 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3228 // If the reference count is now zero, delete both the message and its metadata record.
3229 if (smi.meta_refcount == 0) {
3230 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3232 // Call delete hooks with NULL room to show it has gone altogether
3233 PerformDeleteHooks(NULL, msgnum);
3235 // Remove from message base
3237 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3238 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3240 // Remove metadata record
3241 delnum = (0L - msgnum);
3242 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3247 // Write a generic object to this room
3248 // Returns the message number of the written object, in case you need it.
3249 long CtdlWriteObject(char *req_room, // Room to stuff it in
3250 char *content_type, // MIME type of this object
3251 char *raw_message, // Data to be written
3252 off_t raw_length, // Size of raw_message
3253 struct ctdluser *is_mailbox, // Mailbox room?
3254 int is_binary, // Is encoding necessary?
3255 unsigned int flags // Internal save flags
3257 struct ctdlroom qrbuf;
3258 char roomname[ROOMNAMELEN];
3259 struct CtdlMessage *msg;
3260 StrBuf *encoded_message = NULL;
3262 if (is_mailbox != NULL) {
3263 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3266 safestrncpy(roomname, req_room, sizeof(roomname));
3269 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3272 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3275 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3278 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3279 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3280 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3283 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3286 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3290 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3293 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3296 syslog(LOG_DEBUG, "msgbase: allocating");
3297 msg = malloc(sizeof(struct CtdlMessage));
3298 memset(msg, 0, sizeof(struct CtdlMessage));
3299 msg->cm_magic = CTDLMESSAGE_MAGIC;
3300 msg->cm_anon_type = MES_NORMAL;
3301 msg->cm_format_type = 4;
3302 CM_SetField(msg, eAuthor, CC->user.fullname);
3303 CM_SetField(msg, eOriginalRoom, req_room);
3304 msg->cm_flags = flags;
3306 CM_SetAsFieldSB(msg, eMessageText, &encoded_message);
3308 // Create the requested room if we have to.
3309 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3310 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3313 // Now write the data
3314 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3320 // ************************************************************************/
3321 // * MODULE INITIALIZATION */
3322 // ************************************************************************/
3324 char *ctdl_module_init_msgbase(void) {
3326 FillMsgKeyLookupTable();
3329 // return our module id for the log