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"
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 -> eMesageText
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[] = {
97 /* Important fields */
105 /* Semi-important fields */
110 /* G is not used yet */
113 /* Q is not used yet */
115 /* X is not used yet */
116 /* Z is not used yet */
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
716 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
718 /* Call search module via hook mechanism.
719 * NULL means use any search function available.
720 * otherwise replace with a char * to name of search routine
722 search = CtdlFullTextSearch(search_string);
724 if (array_len(search) > 0) {
728 orig_num_msgs = num_msgs;
730 for (i=0; i<orig_num_msgs; ++i) {
731 for (j=0; j<array_len(search); ++j) {
733 memcpy(&smsgnum, array_get_element_at(search, j), sizeof(long));
734 if (msglist[i] == smsgnum) {
735 msglist[num_msgs++] = msglist[i];
741 num_msgs = 0; /* No messages qualify */
743 if (search != NULL) array_free(search);
745 /* Now that we've purged messages which don't contain the search
746 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
753 * Now iterate through the message list, according to the
754 * criteria supplied by the caller.
757 for (a = 0; a < num_msgs; ++a) {
758 if (server_shutting_down) {
759 if (need_to_free_re) regfree(&re);
761 return num_processed;
763 thismsg = msglist[a];
764 if (mode == MSGS_ALL) {
768 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
769 if (is_seen) lastold = thismsg;
775 || ((mode == MSGS_OLD) && (is_seen))
776 || ((mode == MSGS_NEW) && (!is_seen))
777 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
778 || ((mode == MSGS_FIRST) && (a < ref))
779 || ((mode == MSGS_GT) && (thismsg > ref))
780 || ((mode == MSGS_LT) && (thismsg < ref))
781 || ((mode == MSGS_EQ) && (thismsg == ref))
784 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
786 CallBack(lastold, userdata);
792 CallBack(thismsg, userdata);
797 if (need_to_free_re) regfree(&re);
800 * We cache the most recent msglist in order to do security checks later
802 if (CC->client_socket > 0) {
803 if (CC->cached_msglist != NULL) {
804 free(CC->cached_msglist);
806 CC->cached_msglist = msglist;
807 CC->cached_num_msgs = num_msgs;
813 return num_processed;
818 * memfmout() - Citadel text formatter and paginator.
819 * Although the original purpose of this routine was to format
820 * text to the reader's screen width, all we're really using it
821 * for here is to format text out to 80 columns before sending it
822 * to the client. The client software may reformat it again.
825 char *mptr, /* where are we going to get our text from? */
826 const char *nl /* string to terminate lines with */
829 unsigned char ch = 0;
836 while (ch=*(mptr++), ch != 0) {
839 if (client_write(outbuf, len) == -1) {
840 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
844 if (client_write(nl, nllen) == -1) {
845 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
850 else if (ch == '\r') {
851 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
853 else if (isspace(ch)) {
854 if (column > 72) { /* Beyond 72 columns, break on the next space */
855 if (client_write(outbuf, len) == -1) {
856 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
860 if (client_write(nl, nllen) == -1) {
861 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
874 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
875 if (client_write(outbuf, len) == -1) {
876 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
880 if (client_write(nl, nllen) == -1) {
881 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
889 if (client_write(outbuf, len) == -1) {
890 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
893 client_write(nl, nllen);
900 * Callback function for mime parser that simply lists the part
902 void list_this_part(char *name, char *filename, char *partnum, char *disp,
903 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
904 char *cbid, void *cbuserdata)
908 ma = (struct ma_info *)cbuserdata;
909 if (ma->is_ma == 0) {
910 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
924 * Callback function for multipart prefix
926 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
927 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
928 char *cbid, void *cbuserdata)
932 ma = (struct ma_info *)cbuserdata;
933 if (!strcasecmp(cbtype, "multipart/alternative")) {
937 if (ma->is_ma == 0) {
938 cprintf("pref=%s|%s\n", partnum, cbtype);
944 * Callback function for multipart sufffix
946 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
947 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
948 char *cbid, void *cbuserdata)
952 ma = (struct ma_info *)cbuserdata;
953 if (ma->is_ma == 0) {
954 cprintf("suff=%s|%s\n", partnum, cbtype);
956 if (!strcasecmp(cbtype, "multipart/alternative")) {
963 * Callback function for mime parser that opens a section for downloading
964 * we use serv_files function here:
966 extern void OpenCmdResult(char *filename, const char *mime_type);
967 void mime_download(char *name, char *filename, char *partnum, char *disp,
968 void *content, char *cbtype, char *cbcharset, size_t length,
969 char *encoding, char *cbid, void *cbuserdata)
973 /* Silently go away if there's already a download open. */
974 if (CC->download_fp != NULL)
978 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
979 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
981 CC->download_fp = tmpfile();
982 if (CC->download_fp == NULL) {
983 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
984 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
988 rv = fwrite(content, length, 1, CC->download_fp);
990 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
991 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
992 fclose(CC->download_fp);
993 CC->download_fp = NULL;
996 fflush(CC->download_fp);
997 rewind(CC->download_fp);
999 OpenCmdResult(filename, cbtype);
1005 * Callback function for mime parser that outputs a section all at once.
1006 * We can specify the desired section by part number *or* content-id.
1008 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1009 void *content, char *cbtype, char *cbcharset, size_t length,
1010 char *encoding, char *cbid, void *cbuserdata)
1012 int *found_it = (int *)cbuserdata;
1015 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1016 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1019 cprintf("%d %d|-1|%s|%s|%s\n",
1026 client_write(content, length);
1031 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
1032 struct CtdlMessage *ret = NULL;
1034 const char *upper_bound;
1036 cit_uint8_t field_header;
1040 upper_bound = Buffer + Length;
1045 // Parse the three bytes that begin EVERY message on disk.
1046 // The first is always 0xFF, the on-disk magic number.
1047 // The second is the anonymous/public type byte.
1048 // The third is the format type byte (vari, fixed, or MIME).
1052 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1055 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1056 memset(ret, 0, sizeof(struct CtdlMessage));
1058 ret->cm_magic = CTDLMESSAGE_MAGIC;
1059 ret->cm_anon_type = *mptr++; // Anon type byte
1060 ret->cm_format_type = *mptr++; // Format type byte
1062 // The rest is zero or more arbitrary fields. Load them in.
1063 // We're done when we encounter either a zero-length field or
1064 // have just processed the 'M' (message text) field.
1067 field_header = '\0';
1070 while (field_header == '\0') { // work around possibly buggy messages
1071 if (mptr >= upper_bound) {
1074 field_header = *mptr++;
1076 if (mptr >= upper_bound) {
1079 which = field_header;
1082 CM_SetField(ret, which, mptr);
1084 mptr += len + 1; // advance to next field
1086 } while ((mptr < upper_bound) && (field_header != 'M'));
1091 // Load a message from disk into memory.
1092 // This is used by CtdlOutputMsg() and other fetch functions.
1094 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1095 // using the CM_Free(); function.
1097 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1098 struct cdbdata dmsgtext;
1099 struct CtdlMessage *ret = NULL;
1101 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1102 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1103 if (dmsgtext.ptr == NULL) {
1104 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1108 if (dmsgtext.ptr[dmsgtext.len - 1] != '\0') {
1109 // FIXME LMDB cannot write to immutable memory
1110 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1111 dmsgtext.ptr[dmsgtext.len - 1] = '\0';
1114 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext.ptr, dmsgtext.len);
1119 // Always make sure there's something in the msg text field. If
1120 // it's NULL, the message text is most likely stored separately,
1121 // so go ahead and fetch that. Failing that, just set a dummy
1122 // body so other code doesn't barf.
1124 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1125 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1126 if (dmsgtext.ptr != NULL) {
1127 CM_SetField(ret, eMesageText, dmsgtext.ptr);
1130 if (CM_IsEmpty(ret, eMesageText)) {
1131 CM_SetField(ret, eMesageText, "\r\n\r\n (no text)\r\n");
1138 // Pre callback function for multipart/alternative
1140 // NOTE: this differs from the standard behavior for a reason. Normally when
1141 // displaying multipart/alternative you want to show the _last_ usable
1142 // format in the message. Here we show the _first_ one, because it's
1143 // usually text/plain. Since this set of functions is designed for text
1144 // output to non-MIME-aware clients, this is the desired behavior.
1146 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1147 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1148 char *cbid, void *cbuserdata)
1152 ma = (struct ma_info *)cbuserdata;
1153 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1154 if (!strcasecmp(cbtype, "multipart/alternative")) {
1158 if (!strcasecmp(cbtype, "message/rfc822")) {
1165 // Post callback function for multipart/alternative
1167 void fixed_output_post(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)
1173 ma = (struct ma_info *)cbuserdata;
1174 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1175 if (!strcasecmp(cbtype, "multipart/alternative")) {
1179 if (!strcasecmp(cbtype, "message/rfc822")) {
1185 // Inline callback function for mime parser that wants to display text
1186 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1187 void *content, char *cbtype, char *cbcharset, size_t length,
1188 char *encoding, char *cbid, void *cbuserdata)
1195 ma = (struct ma_info *)cbuserdata;
1198 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1199 partnum, filename, cbtype, (long)length
1202 // If we're in the middle of a multipart/alternative scope and
1203 // we've already printed another section, skip this one.
1204 if ( (ma->is_ma) && (ma->did_print) ) {
1205 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1210 if ( (!strcasecmp(cbtype, "text/plain"))
1211 || (IsEmptyStr(cbtype)) ) {
1214 client_write(wptr, length);
1215 if (wptr[length-1] != '\n') {
1222 if (!strcasecmp(cbtype, "text/html")) {
1223 ptr = html_to_ascii(content, length, 80, 0);
1225 client_write(ptr, wlen);
1226 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1233 if (ma->use_fo_hooks) {
1234 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1239 if (strncasecmp(cbtype, "multipart/", 10)) {
1240 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1241 partnum, filename, cbtype, (long)length);
1247 // The client is elegant and sophisticated and wants to be choosy about
1248 // MIME content types, so figure out which multipart/alternative part
1249 // we're going to send.
1251 // We use a system of weights. When we find a part that matches one of the
1252 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1253 // and then set ma->chosen_pref to that MIME type's position in our preference
1254 // list. If we then hit another match, we only replace the first match if
1255 // the preference value is lower.
1256 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1257 void *content, char *cbtype, char *cbcharset, size_t length,
1258 char *encoding, char *cbid, void *cbuserdata)
1264 ma = (struct ma_info *)cbuserdata;
1266 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1267 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1268 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1269 if (i < ma->chosen_pref) {
1270 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1271 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 ma->chosen_pref = i;
1279 // Now that we've chosen our preferred part, output it.
1280 void output_preferred(char *name,
1294 int add_newline = 0;
1297 char *decoded = NULL;
1298 size_t bytes_decoded;
1301 ma = (struct ma_info *)cbuserdata;
1303 // This is not the MIME part you're looking for...
1304 if (strcasecmp(partnum, ma->chosen_part)) return;
1306 // If the content-type of this part is in our preferred formats
1307 // list, we can simply output it verbatim.
1308 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1309 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1310 if (!strcasecmp(buf, cbtype)) {
1311 /* Yeah! Go! W00t!! */
1312 if (ma->dont_decode == 0)
1313 rc = mime_decode_now (content,
1319 break; // Give us the chance, maybe theres another one.
1321 if (rc == 0) text_content = (char *)content;
1323 text_content = decoded;
1324 length = bytes_decoded;
1327 if (text_content[length-1] != '\n') {
1330 cprintf("Content-type: %s", cbtype);
1331 if (!IsEmptyStr(cbcharset)) {
1332 cprintf("; charset=%s", cbcharset);
1334 cprintf("\nContent-length: %d\n",
1335 (int)(length + add_newline) );
1336 if (!IsEmptyStr(encoding)) {
1337 cprintf("Content-transfer-encoding: %s\n", encoding);
1340 cprintf("Content-transfer-encoding: 7bit\n");
1342 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1344 if (client_write(text_content, length) == -1)
1346 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1349 if (add_newline) cprintf("\n");
1350 if (decoded != NULL) free(decoded);
1355 // No translations required or possible: output as text/plain
1356 cprintf("Content-type: text/plain\n\n");
1358 if (ma->dont_decode == 0)
1359 rc = mime_decode_now (content,
1365 return; // Give us the chance, maybe theres another one.
1367 if (rc == 0) text_content = (char *)content;
1369 text_content = decoded;
1370 length = bytes_decoded;
1373 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1374 if (decoded != NULL) free(decoded);
1379 char desired_section[64];
1385 // Callback function
1386 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1387 void *content, char *cbtype, char *cbcharset, size_t length,
1388 char *encoding, char *cbid, void *cbuserdata)
1390 struct encapmsg *encap;
1392 encap = (struct encapmsg *)cbuserdata;
1394 // Only proceed if this is the desired section...
1395 if (!strcasecmp(encap->desired_section, partnum)) {
1396 encap->msglen = length;
1397 encap->msg = malloc(length + 2);
1398 memcpy(encap->msg, content, length);
1404 // Determine whether the specified message exists in the cached_msglist
1405 // (This is a security check)
1406 int check_cached_msglist(long msgnum) {
1408 // cases in which we skip the check
1409 if (!CC) return om_ok; // not a session
1410 if (CC->client_socket <= 0) return om_ok; // not a client session
1411 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1412 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1414 // Do a binary search within the cached_msglist for the requested msgnum
1416 int max = (CC->cached_num_msgs - 1);
1418 while (max >= min) {
1419 int middle = min + (max-min) / 2 ;
1420 if (msgnum == CC->cached_msglist[middle]) {
1423 if (msgnum > CC->cached_msglist[middle]) {
1431 return om_access_denied;
1435 // Get a message off disk. (returns om_* values found in msgbase.h)
1436 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1437 int mode, // how would you like that message?
1438 int headers_only, // eschew the message body?
1439 int do_proto, // do Citadel protocol responses?
1440 int crlf, // Use CRLF newlines instead of LF?
1441 char *section, // NULL or a message/rfc822 section
1442 int flags, // various flags; see msgbase.h
1447 struct CtdlMessage *TheMessage = NULL;
1448 int retcode = CIT_OK;
1449 struct encapmsg encap;
1452 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1454 (section ? section : "<>")
1457 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1460 if (r == om_not_logged_in) {
1461 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1464 cprintf("%d An unknown error has occurred.\n", ERROR);
1470 // Check to make sure the message is actually IN this room
1471 r = check_cached_msglist(msg_num);
1472 if (r == om_access_denied) {
1473 // Not in the cache? We get ONE shot to check it again.
1474 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1475 r = check_cached_msglist(msg_num);
1478 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1479 msg_num, CC->room.QRname
1482 if (r == om_access_denied) {
1483 cprintf("%d message %ld was not found in this room\n",
1484 ERROR + HIGHER_ACCESS_REQUIRED,
1492 // Fetch the message from disk. If we're in HEADERS_FAST mode, request that we don't even bother loading the body into memory.
1493 if (headers_only == HEADERS_FAST) {
1494 TheMessage = CtdlFetchMessage(msg_num, 0);
1497 TheMessage = CtdlFetchMessage(msg_num, 1);
1500 if (TheMessage == NULL) {
1501 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1502 ERROR + MESSAGE_NOT_FOUND, msg_num);
1503 return(om_no_such_msg);
1506 // Here is the weird form of this command, to process only an encapsulated message/rfc822 section.
1507 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1508 memset(&encap, 0, sizeof encap);
1509 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1510 mime_parser(CM_RANGE(TheMessage, eMesageText),
1511 *extract_encapsulated_message,
1512 NULL, NULL, (void *)&encap, 0
1515 if ((Author != NULL) && (*Author == NULL)) {
1517 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1519 if ((Address != NULL) && (*Address == NULL)) {
1521 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1523 if ((MessageID != NULL) && (*MessageID == NULL)) {
1525 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1527 CM_Free(TheMessage);
1531 encap.msg[encap.msglen] = 0;
1532 TheMessage = convert_internet_message(encap.msg);
1533 encap.msg = NULL; // no free() here, TheMessage owns it now
1535 // Now we let it fall through to the bottom of this function, because TheMessage now contains the
1536 // encapsulated message instead of the top-level message. Isn't that neat?
1540 cprintf("%d msg %ld has no part %s\n", ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1542 retcode = om_no_such_msg;
1547 // Ok, output the message now
1548 if (retcode == CIT_OK) {
1549 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1551 if ((Author != NULL) && (*Author == NULL)) {
1553 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1555 if ((Address != NULL) && (*Address == NULL)) {
1557 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1559 if ((MessageID != NULL) && (*MessageID == NULL)) {
1561 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1564 CM_Free(TheMessage);
1570 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1573 char display_name[256];
1575 /* begin header processing loop for Citadel message format */
1576 safestrncpy(display_name, "<unknown>", sizeof display_name);
1577 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1578 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1579 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1580 safestrncpy(display_name, "****", sizeof display_name);
1582 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1583 safestrncpy(display_name, "anonymous", sizeof display_name);
1586 safestrncpy(display_name, buf, sizeof display_name);
1588 if ((is_room_aide())
1589 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1590 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1591 size_t tmp = strlen(display_name);
1592 snprintf(&display_name[tmp],
1593 sizeof display_name - tmp,
1598 /* Now spew the header fields in the order we like them. */
1599 for (i=0; i< NDiskFields; ++i) {
1601 Field = FieldOrder[i];
1602 if (Field != eMesageText) {
1603 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1604 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1605 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1607 if (Field == eAuthor) {
1609 cprintf("%s=%s\n", msgkeys[Field], display_name);
1612 /* Masquerade display name if needed */
1615 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1618 /* Give the client a hint about whether the message originated locally */
1619 if (Field == erFc822Addr) {
1620 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1621 cprintf("locl=yes\n"); // message originated locally.
1633 void OutputRFC822MsgHeaders(
1634 struct CtdlMessage *TheMessage,
1635 int flags, /* should the message be exported clean */
1636 const char *nl, int nlen,
1637 char *mid, long sizeof_mid,
1638 char *suser, long sizeof_suser,
1639 char *luser, long sizeof_luser,
1640 char *fuser, long sizeof_fuser,
1641 char *snode, long sizeof_snode)
1643 char datestamp[100];
1644 int subject_found = 0;
1651 for (i = 0; i < NDiskFields; ++i) {
1652 if (TheMessage->cm_fields[FieldOrder[i]]) {
1653 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1654 switch (FieldOrder[i]) {
1656 safestrncpy(luser, mptr, sizeof_luser);
1657 safestrncpy(suser, mptr, sizeof_suser);
1660 if ((flags & QP_EADDR) != 0) {
1661 mptr = qp_encode_email_addrs(mptr);
1663 sanitize_truncated_recipient(mptr);
1664 cprintf("CC: %s%s", mptr, nl);
1667 cprintf("Return-Path: %s%s", mptr, nl);
1670 cprintf("List-ID: %s%s", mptr, nl);
1673 if ((flags & QP_EADDR) != 0)
1674 mptr = qp_encode_email_addrs(mptr);
1676 while ((*hptr != '\0') && isspace(*hptr))
1678 if (!IsEmptyStr(hptr))
1679 cprintf("Envelope-To: %s%s", hptr, nl);
1682 cprintf("Subject: %s%s", mptr, nl);
1686 safestrncpy(mid, mptr, sizeof_mid);
1689 safestrncpy(fuser, mptr, sizeof_fuser);
1692 if (haschar(mptr, '@') == 0) {
1693 sanitize_truncated_recipient(mptr);
1694 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1698 if ((flags & QP_EADDR) != 0) {
1699 mptr = qp_encode_email_addrs(mptr);
1701 sanitize_truncated_recipient(mptr);
1702 cprintf("To: %s", mptr);
1707 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1708 cprintf("Date: %s%s", datestamp, nl);
1711 cprintf("References: ");
1712 k = num_tokens(mptr, '|');
1713 for (j=0; j<k; ++j) {
1714 extract_token(buf, mptr, j, '|', sizeof buf);
1715 cprintf("<%s>", buf);
1726 while ((*hptr != '\0') && isspace(*hptr))
1728 if (!IsEmptyStr(hptr))
1729 cprintf("Reply-To: %s%s", mptr, nl);
1741 /* these don't map to mime message headers. */
1744 if (mptr != mpptr) {
1749 if (subject_found == 0) {
1750 cprintf("Subject: (no subject)%s", nl);
1755 void Dump_RFC822HeadersBody(
1756 struct CtdlMessage *TheMessage,
1757 int headers_only, /* eschew the message body? */
1758 int flags, /* should the bessage be exported clean? */
1759 const char *nl, int nlen)
1761 cit_uint8_t prev_ch;
1763 const char *StartOfText = StrBufNOTNULL;
1766 int nllen = strlen(nl);
1770 mptr = TheMessage->cm_fields[eMesageText];
1773 while (*mptr != '\0') {
1774 if (*mptr == '\r') {
1781 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1783 eoh = *(mptr+1) == '\n';
1787 StartOfText = strchr(StartOfText, '\n');
1788 StartOfText = strchr(StartOfText, '\n');
1791 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1792 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1793 ((headers_only != HEADERS_NONE) &&
1794 (headers_only != HEADERS_ONLY))
1796 if (*mptr == '\n') {
1797 memcpy(&outbuf[outlen], nl, nllen);
1799 outbuf[outlen] = '\0';
1802 outbuf[outlen++] = *mptr;
1806 if (flags & ESC_DOT) {
1807 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1808 outbuf[outlen++] = '.';
1813 if (outlen > 1000) {
1814 if (client_write(outbuf, outlen) == -1) {
1815 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1818 lfSent = (outbuf[outlen - 1] == '\n');
1823 client_write(outbuf, outlen);
1824 lfSent = (outbuf[outlen - 1] == '\n');
1827 client_write(nl, nlen);
1831 /* If the format type on disk is 1 (fixed-format), then we want
1832 * everything to be output completely literally ... regardless of
1833 * what message transfer format is in use.
1835 void DumpFormatFixed(
1836 struct CtdlMessage *TheMessage,
1837 int mode, /* how would you like that message? */
1838 const char *nl, int nllen)
1846 mptr = TheMessage->cm_fields[eMesageText];
1848 if (mode == MT_MIME) {
1849 cprintf("Content-type: text/plain\n\n");
1853 while (ch = *mptr++, ch > 0) {
1857 if ((buflen > 250) && (!xlline)){
1861 while ((buflen > 0) &&
1862 (!isspace(buf[buflen])))
1868 mptr -= tbuflen - buflen;
1874 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1875 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1880 memcpy (&buf[buflen], nl, nllen);
1884 if (client_write(buf, buflen) == -1) {
1885 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1897 if (!IsEmptyStr(buf)) {
1898 cprintf("%s%s", buf, nl);
1903 // Get a message off disk. (returns om_* values found in msgbase.h)
1904 int CtdlOutputPreLoadedMsg(
1905 struct CtdlMessage *TheMessage,
1906 int mode, // how would you like that message?
1907 int headers_only, // eschew the message body?
1908 int do_proto, // do Citadel protocol responses?
1909 int crlf, // Use CRLF newlines instead of LF?
1910 int flags // should the bessage be exported clean?
1913 const char *nl; // newline string
1917 // Buffers needed for RFC822 translation. These are all filled
1918 // using functions that are bounds-checked, and therefore we can
1919 // make them substantially smaller than SIZ.
1926 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1927 ((TheMessage == NULL) ? "NULL" : "not null"),
1928 mode, headers_only, do_proto, crlf
1931 strcpy(mid, "unknown");
1932 nl = (crlf ? "\r\n" : "\n");
1933 nlen = crlf ? 2 : 1;
1935 if (!CM_IsValidMsg(TheMessage)) {
1936 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1937 return(om_no_such_msg);
1940 // Suppress envelope recipients if required to avoid disclosing BCC addresses.
1941 // Pad it with spaces in order to avoid changing the RFC822 length of the message.
1942 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1943 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1946 // Are we downloading a MIME component?
1947 if (mode == MT_DOWNLOAD) {
1948 if (TheMessage->cm_format_type != FMT_RFC822) {
1950 cprintf("%d This is not a MIME message.\n", ERROR + ILLEGAL_VALUE);
1953 else if (CC->download_fp != NULL) {
1955 cprintf( "%d You already have a download open.\n", ERROR + RESOURCE_BUSY);
1959 // Parse the message text component
1960 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_download, NULL, NULL, NULL, 0);
1962 // If there's no file open by this time, the requested * section wasn't found, so print an error
1963 if (CC->download_fp == NULL) {
1965 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
1969 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1972 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1973 // in a single server operation instead of opening a download file.
1974 if (mode == MT_SPEW_SECTION) {
1975 if (TheMessage->cm_format_type != FMT_RFC822) {
1977 cprintf("%d This is not a MIME message.\n",
1978 ERROR + ILLEGAL_VALUE);
1981 // Locate and parse the component specified by the caller
1983 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1985 // If section wasn't found, print an error
1988 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
1992 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1995 // now for the user-mode message reading loops
1996 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1998 // Does the caller want to skip the headers?
1999 if (headers_only == HEADERS_NONE) goto START_TEXT;
2001 // Tell the client which format type we're using.
2002 if ( (mode == MT_CITADEL) && (do_proto) ) {
2003 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2006 // nhdr=yes means that we're only displaying headers, no body
2007 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2008 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2011 cprintf("nhdr=yes\n");
2014 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2015 OutputCtdlMsgHeaders(TheMessage, do_proto);
2018 // begin header processing loop for RFC822 transfer format
2023 if (mode == MT_RFC822) {
2024 OutputRFC822MsgHeaders(
2029 suser, sizeof(suser),
2030 luser, sizeof(luser),
2031 fuser, sizeof(fuser),
2032 snode, sizeof(snode)
2036 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2037 suser[i] = tolower(suser[i]);
2038 if (!isalnum(suser[i])) suser[i]='_';
2041 if (mode == MT_RFC822) {
2042 // Construct a fun message id
2043 cprintf("Message-ID: <%s", mid);
2044 if (strchr(mid, '@')==NULL) {
2045 cprintf("@%s", snode);
2049 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2050 cprintf("From: \"----\" <x@x.org>%s", nl);
2052 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2053 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2055 else if (!IsEmptyStr(fuser)) {
2056 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2059 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2062 // Blank line signifying RFC822 end-of-headers
2063 if (TheMessage->cm_format_type != FMT_RFC822) {
2068 // end header processing loop ... at this point, we're in the text
2070 if (headers_only == HEADERS_FAST) goto DONE;
2072 // Tell the client about the MIME parts in this message
2073 if (TheMessage->cm_format_type == FMT_RFC822) {
2074 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2075 memset(&ma, 0, sizeof(struct ma_info));
2076 mime_parser(CM_RANGE(TheMessage, eMesageText),
2077 (do_proto ? *list_this_part : NULL),
2078 (do_proto ? *list_this_pref : NULL),
2079 (do_proto ? *list_this_suff : NULL),
2082 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2083 Dump_RFC822HeadersBody(
2092 if (headers_only == HEADERS_ONLY) {
2096 // signify start of msg text
2097 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2098 if (do_proto) cprintf("text\n");
2101 if (TheMessage->cm_format_type == FMT_FIXED)
2104 mode, // how would you like that message?
2107 // If the message on disk is format 0 (Citadel vari-format), we
2108 // output using the formatter at 80 columns. This is the final output
2109 // form if the transfer format is RFC822, but if the transfer format
2110 // is Citadel proprietary, it'll still work, because the indentation
2111 // for new paragraphs is correct and the client will reformat the
2112 // message to the reader's screen width.
2114 if (TheMessage->cm_format_type == FMT_CITADEL) {
2115 if (mode == MT_MIME) {
2116 cprintf("Content-type: text/x-citadel-variformat\n\n");
2118 memfmout(TheMessage->cm_fields[eMesageText], nl);
2121 // If the message on disk is format 4 (MIME), we've gotta hand it
2122 // off to the MIME parser. The client has already been told that
2123 // this message is format 1 (fixed format), so the callback function
2124 // we use will display those parts as-is.
2126 if (TheMessage->cm_format_type == FMT_RFC822) {
2127 memset(&ma, 0, sizeof(struct ma_info));
2129 if (mode == MT_MIME) {
2130 ma.use_fo_hooks = 0;
2131 strcpy(ma.chosen_part, "1");
2132 ma.chosen_pref = 9999;
2133 ma.dont_decode = CC->msg4_dont_decode;
2134 mime_parser(CM_RANGE(TheMessage, eMesageText),
2135 *choose_preferred, *fixed_output_pre,
2136 *fixed_output_post, (void *)&ma, 1);
2137 mime_parser(CM_RANGE(TheMessage, eMesageText),
2138 *output_preferred, NULL, NULL, (void *)&ma, 1);
2141 ma.use_fo_hooks = 1;
2142 mime_parser(CM_RANGE(TheMessage, eMesageText),
2143 *fixed_output, *fixed_output_pre,
2144 *fixed_output_post, (void *)&ma, 0);
2149 DONE: // now we're done
2150 if (do_proto) cprintf("000\n");
2154 // Save one or more message pointers into a specified room
2155 // (Returns 0 for success, nonzero for failure)
2156 // roomname may be NULL to use the current room
2158 // Note that the 'supplied_msg' field may be set to NULL, in which case
2159 // the message will be fetched from disk, by number, if we need to perform
2160 // replication checks. This adds an additional database read, so if the
2161 // caller already has the message in memory then it should be supplied. (Obviously
2162 // this mode of operation only works if we're saving a single message.)
2164 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2165 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2168 char hold_rm[ROOMNAMELEN];
2169 struct cdbdata *cdbfr;
2172 long highest_msg = 0L;
2175 struct CtdlMessage *msg = NULL;
2177 long *msgs_to_be_merged = NULL;
2178 int num_msgs_to_be_merged = 0;
2181 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2182 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2185 strcpy(hold_rm, CC->room.QRname);
2188 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2189 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2190 if (num_newmsgs > 1) supplied_msg = NULL;
2192 // Now the regular stuff
2193 if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
2194 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2195 return(ERROR + ROOM_NOT_FOUND);
2198 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2199 num_msgs_to_be_merged = 0;
2200 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2202 // Create a list of msgid's which were supplied by the caller, but do
2203 // not already exist in the target room. It is absolutely taboo to
2204 // have more than one reference to the same message in a room.
2205 for (i=0; i<num_newmsgs; ++i) {
2207 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2208 if (msglist[j] == newmsgidlist[i]) {
2213 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2217 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2219 // Now merge the new messages
2220 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2221 if (msglist == NULL) {
2222 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2223 free(msgs_to_be_merged);
2225 return (ERROR + INTERNAL_ERROR);
2227 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2228 num_msgs += num_msgs_to_be_merged;
2230 // Sort the message list, so all the msgid's are in order
2231 num_msgs = sort_msglist(msglist, num_msgs);
2233 // Determine the highest message number
2234 highest_msg = msglist[num_msgs - 1];
2236 // Write it back to disk.
2237 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2239 // Free up the memory we used.
2242 // Update the highest-message pointer and unlock the room.
2243 CC->room.QRhighest = highest_msg;
2244 CtdlPutRoomLock(&CC->room);
2246 // Perform replication checks if necessary
2247 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2248 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2250 for (i=0; i<num_msgs_to_be_merged; ++i) {
2251 msgid = msgs_to_be_merged[i];
2253 if (supplied_msg != NULL) {
2257 msg = CtdlFetchMessage(msgid, 0);
2261 ReplicationChecks(msg);
2263 // If the message has an Exclusive ID, index that...
2264 if (!CM_IsEmpty(msg, eExclusiveID)) {
2265 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2268 // Free up the memory we may have allocated
2269 if (msg != supplied_msg) {
2278 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2281 // Submit this room for processing by hooks
2282 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2283 if (total_roomhook_errors) {
2284 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2287 // Go back to the room we were in before we wandered here...
2288 CtdlGetRoom(&CC->room, hold_rm);
2290 // Bump the reference count for all messages which were merged
2291 if (!suppress_refcount_adj) {
2292 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2295 // Free up memory...
2296 if (msgs_to_be_merged != NULL) {
2297 free(msgs_to_be_merged);
2305 // This is the same as CtdlSaveMsgPointersInRoom() but it only accepts a single message.
2306 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
2307 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2311 // Message base operation to save a new message to the message store
2312 // (returns new message number)
2314 // This is the back end for CtdlSubmitMsg() and should not be directly
2315 // called by server-side modules.
2316 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2323 // If the message is big, set its body aside for storage elsewhere and we hide the message body from the serializer
2324 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2326 holdM = msg->cm_fields[eMesageText];
2327 msg->cm_fields[eMesageText] = NULL;
2328 holdMLen = msg->cm_lengths[eMesageText];
2329 msg->cm_lengths[eMesageText] = 0;
2332 // Serialize our data structure for storage in the database
2333 CtdlSerializeMessage(&smr, msg);
2336 // put the message body back into the message
2337 msg->cm_fields[eMesageText] = holdM;
2338 msg->cm_lengths[eMesageText] = holdMLen;
2343 cprintf("%d Unable to serialize message\n", ERROR + INTERNAL_ERROR);
2346 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2351 // Write our little bundle of joy into the message base
2352 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2354 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2358 retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
2360 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2365 // Free the memory we used for the serialized message
2371 long send_message(struct CtdlMessage *msg) {
2377 // Get a new message number
2378 newmsgid = get_new_message_number();
2380 // Generate an ID if we don't have one already
2381 if (CM_IsEmpty(msg, emessageId)) {
2382 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2383 (long unsigned int) time(NULL),
2384 (long unsigned int) newmsgid,
2385 CtdlGetConfigStr("c_fqdn")
2387 CM_SetField(msg, emessageId, msgidbuf);
2390 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2396 // Return the *local* message ID to the caller (even if we're storing a remotely originated message)
2401 // Serialize a struct CtdlMessage into the format used on disk.
2403 // This function loads up a "struct ser_ret" (defined in server.h) which
2404 // contains the length of the serialized message and a pointer to the
2405 // serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2406 void CtdlSerializeMessage(struct ser_ret *ret, // return values
2407 struct CtdlMessage *msg) // unserialized msg
2412 // Check for valid message format
2413 if (CM_IsValidMsg(msg) == 0) {
2414 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2421 for (i=0; i < NDiskFields; ++i)
2422 if (msg->cm_fields[FieldOrder[i]] != NULL)
2423 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2425 ret->ser = malloc(ret->len);
2426 if (ret->ser == NULL) {
2427 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2434 ret->ser[1] = msg->cm_anon_type;
2435 ret->ser[2] = msg->cm_format_type;
2438 for (i=0; i < NDiskFields; ++i) {
2439 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2440 ret->ser[wlen++] = (char)FieldOrder[i];
2442 memcpy(&ret->ser[wlen],
2443 msg->cm_fields[FieldOrder[i]],
2444 msg->cm_lengths[FieldOrder[i]] + 1);
2446 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2450 if (ret->len != wlen) {
2451 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2459 * Check to see if any messages already exist in the current room which
2460 * carry the same Exclusive ID as this one. If any are found, delete them.
2462 void ReplicationChecks(struct CtdlMessage *msg) {
2463 long old_msgnum = (-1L);
2465 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2467 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2469 /* No exclusive id? Don't do anything. */
2470 if (msg == NULL) return;
2471 if (CM_IsEmpty(msg, eExclusiveID)) return;
2473 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2474 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2476 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2477 if (old_msgnum > 0L) {
2478 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2479 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2485 * Save a message to disk and submit it into the delivery system.
2487 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2488 struct recptypes *recps, /* recipients (if mail) */
2489 const char *force /* force a particular room? */
2491 char hold_rm[ROOMNAMELEN];
2492 char actual_rm[ROOMNAMELEN];
2493 char force_room[ROOMNAMELEN];
2494 char content_type[SIZ]; /* We have to learn this */
2495 char recipient[SIZ];
2496 char bounce_to[1024];
2499 const char *mptr = NULL;
2500 struct ctdluser userbuf;
2502 struct MetaData smi;
2503 char *collected_addresses = NULL;
2504 struct addresses_to_be_filed *aptr = NULL;
2505 StrBuf *saved_rfc822_version = NULL;
2506 int qualified_for_journaling = 0;
2508 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2509 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2511 /* If this message has no timestamp, we take the liberty of
2512 * giving it one, right now.
2514 if (CM_IsEmpty(msg, eTimestamp)) {
2515 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2518 /* If this message has no path, we generate one.
2520 if (CM_IsEmpty(msg, eMessagePath)) {
2521 if (!CM_IsEmpty(msg, eAuthor)) {
2522 CM_CopyField(msg, eMessagePath, eAuthor);
2523 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2524 if (isspace(msg->cm_fields[eMessagePath][a])) {
2525 msg->cm_fields[eMessagePath][a] = ' ';
2530 CM_SetField(msg, eMessagePath, "unknown");
2534 if (force == NULL) {
2535 force_room[0] = '\0';
2538 strcpy(force_room, force);
2541 /* Learn about what's inside, because it's what's inside that counts */
2542 if (CM_IsEmpty(msg, eMesageText)) {
2543 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2547 switch (msg->cm_format_type) {
2549 strcpy(content_type, "text/x-citadel-variformat");
2552 strcpy(content_type, "text/plain");
2555 strcpy(content_type, "text/plain");
2556 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2559 safestrncpy(content_type, &mptr[13], sizeof content_type);
2560 string_trim(content_type);
2561 aptr = content_type;
2562 while (!IsEmptyStr(aptr)) {
2574 /* Goto the correct room */
2575 room = (recps) ? CC->room.QRname : SENTITEMS;
2576 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2577 strcpy(hold_rm, CC->room.QRname);
2578 strcpy(actual_rm, CC->room.QRname);
2579 if (recps != NULL) {
2580 strcpy(actual_rm, SENTITEMS);
2583 /* If the user is a twit, move to the twit room for posting */
2585 if (CC->user.axlevel == AxProbU) {
2586 strcpy(hold_rm, actual_rm);
2587 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2588 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2592 /* ...or if this message is destined for Aide> then go there. */
2593 if (!IsEmptyStr(force_room)) {
2594 strcpy(actual_rm, force_room);
2597 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2598 if (strcasecmp(actual_rm, CC->room.QRname)) {
2599 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2603 * If this message has no O (room) field, generate one.
2605 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2606 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2609 /* Perform "before save" hooks (aborting if any return nonzero) */
2610 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2611 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2614 * If this message has an Exclusive ID, and the room is replication
2615 * checking enabled, then do replication checks.
2617 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2618 ReplicationChecks(msg);
2621 /* Save it to disk */
2622 syslog(LOG_DEBUG, "msgbase: saving to disk");
2623 newmsgid = send_message(msg);
2624 if (newmsgid <= 0L) return(-5);
2626 /* Write a supplemental message info record. This doesn't have to
2627 * be a critical section because nobody else knows about this message
2630 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2631 memset(&smi, 0, sizeof(struct MetaData));
2632 smi.meta_msgnum = newmsgid;
2633 smi.meta_refcount = 0;
2634 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2637 * Measure how big this message will be when rendered as RFC822.
2638 * We do this for two reasons:
2639 * 1. We need the RFC822 length for the new metadata record, so the
2640 * POP and IMAP services don't have to calculate message lengths
2641 * while the user is waiting (multiplied by potentially hundreds
2642 * or thousands of messages).
2643 * 2. If journaling is enabled, we will need an RFC822 version of the
2644 * message to attach to the journalized copy.
2646 if (CC->redirect_buffer != NULL) {
2647 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2648 exit(CTDLEXIT_REDIRECT);
2650 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2651 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2652 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2653 saved_rfc822_version = CC->redirect_buffer;
2654 CC->redirect_buffer = NULL;
2658 /* Now figure out where to store the pointers */
2659 syslog(LOG_DEBUG, "msgbase: storing pointers");
2661 /* If this is being done by the networker delivering a private
2662 * message, we want to BYPASS saving the sender's copy (because there
2663 * is no local sender; it would otherwise go to the Trashcan).
2665 if ((!CC->internal_pgm) || (recps == NULL)) {
2666 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2667 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2668 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2672 /* For internet mail, drop a copy in the outbound queue room */
2673 if ((recps != NULL) && (recps->num_internet > 0)) {
2674 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2677 /* If other rooms are specified, drop them there too. */
2678 if ((recps != NULL) && (recps->num_room > 0)) {
2679 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2680 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2681 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2682 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2686 /* Decide where bounces need to be delivered */
2687 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2688 if (CC->logged_in) {
2689 strcpy(bounce_to, CC->user.fullname);
2691 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2692 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2694 recps->bounce_to = bounce_to;
2697 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2699 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2700 if ((recps != NULL) && (recps->num_local > 0)) {
2704 pch = recps->recp_local;
2705 recps->recp_local = recipient;
2706 ntokens = num_tokens(pch, '|');
2707 for (i=0; i<ntokens; ++i) {
2708 extract_token(recipient, pch, i, '|', sizeof recipient);
2709 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2710 if (CtdlGetUser(&userbuf, recipient) == 0) {
2711 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2712 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2713 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2714 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2717 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2718 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2721 recps->recp_local = pch;
2724 /* Perform "after save" hooks */
2725 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2727 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2728 CM_FlushField(msg, eVltMsgNum);
2730 /* Go back to the room we started from */
2731 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2732 if (strcasecmp(hold_rm, CC->room.QRname)) {
2733 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2737 * Any addresses to harvest for someone's address book?
2739 if ( (CC->logged_in) && (recps != NULL) ) {
2740 collected_addresses = harvest_collected_addresses(msg);
2743 if (collected_addresses != NULL) {
2744 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2745 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2746 aptr->roomname = strdup(actual_rm);
2747 aptr->collected_addresses = collected_addresses;
2748 begin_critical_section(S_ATBF);
2751 end_critical_section(S_ATBF);
2755 * Determine whether this message qualifies for journaling.
2757 if (!CM_IsEmpty(msg, eJournal)) {
2758 qualified_for_journaling = 0;
2761 if (recps == NULL) {
2762 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2764 else if (recps->num_local + recps->num_internet > 0) {
2765 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2768 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2773 * Do we have to perform journaling? If so, hand off the saved
2774 * RFC822 version will be handed off to the journaler for background
2775 * submit. Otherwise, we have to free the memory ourselves.
2777 if (saved_rfc822_version != NULL) {
2778 if (qualified_for_journaling) {
2779 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2782 FreeStrBuf(&saved_rfc822_version);
2786 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2787 recps->bounce_to = NULL;
2795 * Convenience function for generating small administrative messages.
2797 long quickie_message(char *from,
2805 struct CtdlMessage *msg;
2806 struct recptypes *recp = NULL;
2808 msg = malloc(sizeof(struct CtdlMessage));
2809 memset(msg, 0, sizeof(struct CtdlMessage));
2810 msg->cm_magic = CTDLMESSAGE_MAGIC;
2811 msg->cm_anon_type = MES_NORMAL;
2812 msg->cm_format_type = format_type;
2814 if (!IsEmptyStr(from)) {
2815 CM_SetField(msg, eAuthor, from);
2817 else if (!IsEmptyStr(fromaddr)) {
2819 CM_SetField(msg, eAuthor, fromaddr);
2820 pAt = strchr(msg->cm_fields[eAuthor], '@');
2822 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2826 msg->cm_fields[eAuthor] = strdup("Citadel");
2829 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr);
2830 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
2831 if (!IsEmptyStr(to)) {
2832 CM_SetField(msg, eRecipient, to);
2833 recp = validate_recipients(to, NULL, 0);
2835 if (!IsEmptyStr(subject)) {
2836 CM_SetField(msg, eMsgSubject, subject);
2838 if (!IsEmptyStr(text)) {
2839 CM_SetField(msg, eMesageText, text);
2842 long msgnum = CtdlSubmitMsg(msg, recp, room);
2844 if (recp != NULL) free_recipients(recp);
2850 * Back end function used by CtdlMakeMessage() and similar functions
2852 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2854 size_t maxlen, // maximum message length
2855 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2856 int crlf // CRLF newlines instead of LF
2864 LineBuf = NewStrBufPlain(NULL, SIZ);
2865 if (exist == NULL) {
2866 Message = NewStrBufPlain(NULL, 4 * SIZ);
2869 Message = NewStrBufDup(exist);
2872 /* Do we need to change leading ".." to "." for SMTP escaping? */
2873 if ((tlen == 1) && (*terminator == '.')) {
2877 /* read in the lines of message text one by one */
2879 if (CtdlClientGetLine(LineBuf) < 0) {
2882 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2885 if ( (!flushing) && (!finished) ) {
2887 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2890 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2893 /* Unescape SMTP-style input of two dots at the beginning of the line */
2894 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2895 StrBufCutLeft(LineBuf, 1);
2897 StrBufAppendBuf(Message, LineBuf, 0);
2900 /* if we've hit the max msg length, flush the rest */
2901 if (StrLength(Message) >= maxlen) {
2905 } while (!finished);
2906 FreeStrBuf(&LineBuf);
2909 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2916 // Back end function used by CtdlMakeMessage() and similar functions
2917 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2919 size_t maxlen, // maximum message length
2920 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2921 int crlf // CRLF newlines instead of LF
2925 Message = CtdlReadMessageBodyBuf(terminator,
2931 if (Message == NULL) {
2935 return SmashStrBuf(&Message);
2940 struct CtdlMessage *CtdlMakeMessage(
2941 struct ctdluser *author, /* author's user structure */
2942 char *recipient, /* NULL if it's not mail */
2943 char *recp_cc, /* NULL if it's not mail */
2944 char *room, /* room where it's going */
2945 int type, /* see MES_ types in header file */
2946 int format_type, /* variformat, plain text, MIME... */
2947 char *fake_name, /* who we're masquerading as */
2948 char *my_email, /* which of my email addresses to use (empty is ok) */
2949 char *subject, /* Subject (optional) */
2950 char *supplied_euid, /* ...or NULL if this is irrelevant */
2951 char *preformatted_text, /* ...or NULL to read text from client */
2952 char *references /* Thread references */
2954 return CtdlMakeMessageLen(
2955 author, /* author's user structure */
2956 recipient, /* NULL if it's not mail */
2957 (recipient)?strlen(recipient) : 0,
2958 recp_cc, /* NULL if it's not mail */
2959 (recp_cc)?strlen(recp_cc): 0,
2960 room, /* room where it's going */
2961 (room)?strlen(room): 0,
2962 type, /* see MES_ types in header file */
2963 format_type, /* variformat, plain text, MIME... */
2964 fake_name, /* who we're masquerading as */
2965 (fake_name)?strlen(fake_name): 0,
2966 my_email, /* which of my email addresses to use (empty is ok) */
2967 (my_email)?strlen(my_email): 0,
2968 subject, /* Subject (optional) */
2969 (subject)?strlen(subject): 0,
2970 supplied_euid, /* ...or NULL if this is irrelevant */
2971 (supplied_euid)?strlen(supplied_euid):0,
2972 preformatted_text, /* ...or NULL to read text from client */
2973 (preformatted_text)?strlen(preformatted_text) : 0,
2974 references, /* Thread references */
2975 (references)?strlen(references):0);
2981 * Build a binary message to be saved on disk.
2982 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2983 * will become part of the message. This means you are no longer
2984 * responsible for managing that memory -- it will be freed along with
2985 * the rest of the fields when CM_Free() is called.)
2987 struct CtdlMessage *CtdlMakeMessageLen(
2988 struct ctdluser *author, /* author's user structure */
2989 char *recipient, /* NULL if it's not mail */
2991 char *recp_cc, /* NULL if it's not mail */
2993 char *room, /* room where it's going */
2995 int type, /* see MES_ types in header file */
2996 int format_type, /* variformat, plain text, MIME... */
2997 char *fake_name, /* who we're masquerading as */
2999 char *my_email, /* which of my email addresses to use (empty is ok) */
3001 char *subject, /* Subject (optional) */
3003 char *supplied_euid, /* ...or NULL if this is irrelevant */
3005 char *preformatted_text, /* ...or NULL to read text from client */
3007 char *references, /* Thread references */
3012 struct CtdlMessage *msg;
3014 StrBuf *FakeEncAuthor = NULL;
3016 msg = malloc(sizeof(struct CtdlMessage));
3017 memset(msg, 0, sizeof(struct CtdlMessage));
3018 msg->cm_magic = CTDLMESSAGE_MAGIC;
3019 msg->cm_anon_type = type;
3020 msg->cm_format_type = format_type;
3022 if (recipient != NULL) rcplen = string_trim(recipient);
3023 if (recp_cc != NULL) cclen = string_trim(recp_cc);
3025 /* Path or Return-Path */
3027 CM_SetField(msg, eMessagePath, my_email);
3029 else if (!IsEmptyStr(author->fullname)) {
3030 CM_SetField(msg, eMessagePath, author->fullname);
3032 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3034 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3035 CM_SetField(msg, eTimestamp, buf);
3038 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3041 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3043 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3044 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3045 FreeStrBuf(&FakeAuthor);
3047 if (!!IsEmptyStr(CC->room.QRname)) {
3048 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3049 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11]);
3052 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
3057 CM_SetField(msg, eRecipient, recipient);
3060 CM_SetField(msg, eCarbonCopY, recp_cc);
3064 CM_SetField(msg, erFc822Addr, my_email);
3066 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3067 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
3070 if (subject != NULL) {
3072 length = string_trim(subject);
3078 while ((subject[i] != '\0') &&
3079 (IsAscii = isascii(subject[i]) != 0 ))
3082 CM_SetField(msg, eMsgSubject, subject);
3083 else /* ok, we've got utf8 in the string. */
3086 rfc2047Subj = rfc2047encode(subject, length);
3087 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3094 CM_SetField(msg, eExclusiveID, supplied_euid);
3098 CM_SetField(msg, eWeferences, references);
3101 if (preformatted_text != NULL) {
3102 CM_SetField(msg, eMesageText, preformatted_text);
3106 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3107 if (MsgBody != NULL) {
3108 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3117 * API function to delete messages which match a set of criteria
3118 * (returns the actual number of messages deleted)
3120 int CtdlDeleteMessages(const char *room_name, // which room
3121 long *dmsgnums, // array of msg numbers to be deleted
3122 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3123 char *content_type // or "" for any. regular expressions expected.
3125 struct ctdlroom qrbuf;
3126 struct cdbdata *cdbfr;
3127 long *msglist = NULL;
3128 long *dellist = NULL;
3131 int num_deleted = 0;
3133 struct MetaData smi;
3136 int need_to_free_re = 0;
3138 if (content_type) if (!IsEmptyStr(content_type)) {
3139 regcomp(&re, content_type, 0);
3140 need_to_free_re = 1;
3142 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3144 /* get room record, obtaining a lock... */
3145 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3146 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3147 if (need_to_free_re) regfree(&re);
3148 return(0); /* room not found */
3151 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3153 dellist = malloc(num_msgs * sizeof(long));
3154 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3155 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3156 int have_more_del = 1;
3158 num_msgs = sort_msglist(msglist, num_msgs);
3159 if (num_dmsgnums > 1) {
3160 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3164 while ((i < num_msgs) && (have_more_del)) {
3167 /* Set/clear a bit for each criterion */
3169 /* 0 messages in the list or a null list means that we are
3170 * interested in deleting any messages which meet the other criteria.
3173 delete_this |= 0x01;
3176 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3181 if (msglist[i] == dmsgnums[j]) {
3182 delete_this |= 0x01;
3185 have_more_del = (j < num_dmsgnums);
3188 if (have_contenttype) {
3189 GetMetaData(&smi, msglist[i]);
3190 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3191 delete_this |= 0x02;
3194 delete_this |= 0x02;
3197 /* Delete message only if all bits are set */
3198 if (delete_this == 0x03) {
3199 dellist[num_deleted++] = msglist[i];
3205 num_msgs = sort_msglist(msglist, num_msgs);
3206 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3209 qrbuf.QRhighest = msglist[num_msgs - 1];
3212 qrbuf.QRhighest = 0;
3215 CtdlPutRoomLock(&qrbuf);
3217 /* Go through the messages we pulled out of the index, and decrement
3218 * their reference counts by 1. If this is the only room the message
3219 * was in, the reference count will reach zero and the message will
3220 * automatically be deleted from the database. We do this in a
3221 * separate pass because there might be plug-in hooks getting called,
3222 * and we don't want that happening during an S_ROOMS critical
3226 for (i=0; i<num_deleted; ++i) {
3227 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3229 AdjRefCountList(dellist, num_deleted, -1);
3231 /* Now free the memory we used, and go away. */
3232 if (msglist != NULL) free(msglist);
3233 if (dellist != NULL) free(dellist);
3234 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3235 if (need_to_free_re) regfree(&re);
3236 return (num_deleted);
3241 * GetMetaData() - Get the supplementary record for a message
3243 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3244 struct cdbdata cdbsmi;
3247 memset(smibuf, 0, sizeof(struct MetaData));
3248 smibuf->meta_msgnum = msgnum;
3249 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3251 /* Use the negative of the message number for its supp record index */
3252 TheIndex = (0L - msgnum);
3254 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3255 if (cdbsmi.ptr == NULL) {
3256 return; /* record not found; leave it alone */
3258 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3264 * PutMetaData() - (re)write supplementary record for a message
3266 void PutMetaData(struct MetaData *smibuf)
3270 /* Use the negative of the message number for the metadata db index */
3271 TheIndex = (0L - smibuf->meta_msgnum);
3273 cdb_store(CDB_MSGMAIN,
3274 &TheIndex, (int)sizeof(long),
3275 smibuf, (int)sizeof(struct MetaData)
3280 // Convenience function to process a big block of AdjRefCount() operations
3281 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3284 for (i = 0; i < nmsg; i++) {
3285 AdjRefCount(msgnum[i], incr);
3290 // AdjRefCount - adjust the reference count for a message.
3291 // We need to delete from disk any message whose reference count reaches zero.
3292 void AdjRefCount(long msgnum, int incr) {
3293 struct MetaData smi;
3296 // This is a *tight* critical section; please keep it that way, as
3297 // it may get called while nested in other critical sections.
3298 // Complicating this any further will surely cause deadlock!
3299 begin_critical_section(S_SUPPMSGMAIN);
3300 GetMetaData(&smi, msgnum);
3301 smi.meta_refcount += incr;
3303 end_critical_section(S_SUPPMSGMAIN);
3304 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3306 // If the reference count is now zero, delete both the message and its metadata record.
3307 if (smi.meta_refcount == 0) {
3308 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3310 // Call delete hooks with NULL room to show it has gone altogether
3311 PerformDeleteHooks(NULL, msgnum);
3313 // Remove from message base
3315 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3316 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3318 // Remove metadata record
3319 delnum = (0L - msgnum);
3320 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3325 // Write a generic object to this room
3326 // Returns the message number of the written object, in case you need it.
3327 long CtdlWriteObject(char *req_room, // Room to stuff it in
3328 char *content_type, // MIME type of this object
3329 char *raw_message, // Data to be written
3330 off_t raw_length, // Size of raw_message
3331 struct ctdluser *is_mailbox, // Mailbox room?
3332 int is_binary, // Is encoding necessary?
3333 unsigned int flags // Internal save flags
3335 struct ctdlroom qrbuf;
3336 char roomname[ROOMNAMELEN];
3337 struct CtdlMessage *msg;
3338 StrBuf *encoded_message = NULL;
3340 if (is_mailbox != NULL) {
3341 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3344 safestrncpy(roomname, req_room, sizeof(roomname));
3347 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3350 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3353 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3356 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3357 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3358 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3361 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3364 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3368 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3371 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3374 syslog(LOG_DEBUG, "msgbase: allocating");
3375 msg = malloc(sizeof(struct CtdlMessage));
3376 memset(msg, 0, sizeof(struct CtdlMessage));
3377 msg->cm_magic = CTDLMESSAGE_MAGIC;
3378 msg->cm_anon_type = MES_NORMAL;
3379 msg->cm_format_type = 4;
3380 CM_SetField(msg, eAuthor, CC->user.fullname);
3381 CM_SetField(msg, eOriginalRoom, req_room);
3382 msg->cm_flags = flags;
3384 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3386 /* Create the requested room if we have to. */
3387 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3388 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3391 /* Now write the data */
3392 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3398 /************************************************************************/
3399 /* MODULE INITIALIZATION */
3400 /************************************************************************/
3402 char *ctdl_module_init_msgbase(void) {
3404 FillMsgKeyLookupTable();
3407 /* return our module id for the log */