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);
1471 * Check to make sure the message is actually IN this room
1473 r = check_cached_msglist(msg_num);
1474 if (r == om_access_denied) {
1475 /* Not in the cache? We get ONE shot to check it again. */
1476 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1477 r = check_cached_msglist(msg_num);
1480 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1481 msg_num, CC->room.QRname
1484 if (r == om_access_denied) {
1485 cprintf("%d message %ld was not found in this room\n",
1486 ERROR + HIGHER_ACCESS_REQUIRED,
1495 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1496 * request that we don't even bother loading the body into memory.
1498 if (headers_only == HEADERS_FAST) {
1499 TheMessage = CtdlFetchMessage(msg_num, 0);
1502 TheMessage = CtdlFetchMessage(msg_num, 1);
1505 if (TheMessage == NULL) {
1506 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1507 ERROR + MESSAGE_NOT_FOUND, msg_num);
1508 return(om_no_such_msg);
1511 /* Here is the weird form of this command, to process only an
1512 * encapsulated message/rfc822 section.
1514 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1515 memset(&encap, 0, sizeof encap);
1516 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1517 mime_parser(CM_RANGE(TheMessage, eMesageText),
1518 *extract_encapsulated_message,
1519 NULL, NULL, (void *)&encap, 0
1522 if ((Author != NULL) && (*Author == NULL))
1525 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1527 if ((Address != NULL) && (*Address == NULL))
1530 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1532 if ((MessageID != NULL) && (*MessageID == NULL))
1535 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1537 CM_Free(TheMessage);
1541 encap.msg[encap.msglen] = 0;
1542 TheMessage = convert_internet_message(encap.msg);
1543 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1545 /* Now we let it fall through to the bottom of this
1546 * function, because TheMessage now contains the
1547 * encapsulated message instead of the top-level
1548 * message. Isn't that neat?
1553 cprintf("%d msg %ld has no part %s\n",
1554 ERROR + MESSAGE_NOT_FOUND,
1558 retcode = om_no_such_msg;
1563 /* Ok, output the message now */
1564 if (retcode == CIT_OK)
1565 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1566 if ((Author != NULL) && (*Author == NULL))
1569 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1571 if ((Address != NULL) && (*Address == NULL))
1574 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1576 if ((MessageID != NULL) && (*MessageID == NULL))
1579 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1582 CM_Free(TheMessage);
1588 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1591 char display_name[256];
1593 /* begin header processing loop for Citadel message format */
1594 safestrncpy(display_name, "<unknown>", sizeof display_name);
1595 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1596 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1597 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1598 safestrncpy(display_name, "****", sizeof display_name);
1600 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1601 safestrncpy(display_name, "anonymous", sizeof display_name);
1604 safestrncpy(display_name, buf, sizeof display_name);
1606 if ((is_room_aide())
1607 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1608 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1609 size_t tmp = strlen(display_name);
1610 snprintf(&display_name[tmp],
1611 sizeof display_name - tmp,
1616 /* Now spew the header fields in the order we like them. */
1617 for (i=0; i< NDiskFields; ++i) {
1619 Field = FieldOrder[i];
1620 if (Field != eMesageText) {
1621 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1622 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1623 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1625 if (Field == eAuthor) {
1627 cprintf("%s=%s\n", msgkeys[Field], display_name);
1630 /* Masquerade display name if needed */
1633 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1636 /* Give the client a hint about whether the message originated locally */
1637 if (Field == erFc822Addr) {
1638 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1639 cprintf("locl=yes\n"); // message originated locally.
1651 void OutputRFC822MsgHeaders(
1652 struct CtdlMessage *TheMessage,
1653 int flags, /* should the message be exported clean */
1654 const char *nl, int nlen,
1655 char *mid, long sizeof_mid,
1656 char *suser, long sizeof_suser,
1657 char *luser, long sizeof_luser,
1658 char *fuser, long sizeof_fuser,
1659 char *snode, long sizeof_snode)
1661 char datestamp[100];
1662 int subject_found = 0;
1669 for (i = 0; i < NDiskFields; ++i) {
1670 if (TheMessage->cm_fields[FieldOrder[i]]) {
1671 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1672 switch (FieldOrder[i]) {
1674 safestrncpy(luser, mptr, sizeof_luser);
1675 safestrncpy(suser, mptr, sizeof_suser);
1678 if ((flags & QP_EADDR) != 0) {
1679 mptr = qp_encode_email_addrs(mptr);
1681 sanitize_truncated_recipient(mptr);
1682 cprintf("CC: %s%s", mptr, nl);
1685 cprintf("Return-Path: %s%s", mptr, nl);
1688 cprintf("List-ID: %s%s", mptr, nl);
1691 if ((flags & QP_EADDR) != 0)
1692 mptr = qp_encode_email_addrs(mptr);
1694 while ((*hptr != '\0') && isspace(*hptr))
1696 if (!IsEmptyStr(hptr))
1697 cprintf("Envelope-To: %s%s", hptr, nl);
1700 cprintf("Subject: %s%s", mptr, nl);
1704 safestrncpy(mid, mptr, sizeof_mid);
1707 safestrncpy(fuser, mptr, sizeof_fuser);
1710 if (haschar(mptr, '@') == 0) {
1711 sanitize_truncated_recipient(mptr);
1712 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1716 if ((flags & QP_EADDR) != 0) {
1717 mptr = qp_encode_email_addrs(mptr);
1719 sanitize_truncated_recipient(mptr);
1720 cprintf("To: %s", mptr);
1725 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1726 cprintf("Date: %s%s", datestamp, nl);
1729 cprintf("References: ");
1730 k = num_tokens(mptr, '|');
1731 for (j=0; j<k; ++j) {
1732 extract_token(buf, mptr, j, '|', sizeof buf);
1733 cprintf("<%s>", buf);
1744 while ((*hptr != '\0') && isspace(*hptr))
1746 if (!IsEmptyStr(hptr))
1747 cprintf("Reply-To: %s%s", mptr, nl);
1759 /* these don't map to mime message headers. */
1762 if (mptr != mpptr) {
1767 if (subject_found == 0) {
1768 cprintf("Subject: (no subject)%s", nl);
1773 void Dump_RFC822HeadersBody(
1774 struct CtdlMessage *TheMessage,
1775 int headers_only, /* eschew the message body? */
1776 int flags, /* should the bessage be exported clean? */
1777 const char *nl, int nlen)
1779 cit_uint8_t prev_ch;
1781 const char *StartOfText = StrBufNOTNULL;
1784 int nllen = strlen(nl);
1788 mptr = TheMessage->cm_fields[eMesageText];
1791 while (*mptr != '\0') {
1792 if (*mptr == '\r') {
1799 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1801 eoh = *(mptr+1) == '\n';
1805 StartOfText = strchr(StartOfText, '\n');
1806 StartOfText = strchr(StartOfText, '\n');
1809 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1810 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1811 ((headers_only != HEADERS_NONE) &&
1812 (headers_only != HEADERS_ONLY))
1814 if (*mptr == '\n') {
1815 memcpy(&outbuf[outlen], nl, nllen);
1817 outbuf[outlen] = '\0';
1820 outbuf[outlen++] = *mptr;
1824 if (flags & ESC_DOT) {
1825 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1826 outbuf[outlen++] = '.';
1831 if (outlen > 1000) {
1832 if (client_write(outbuf, outlen) == -1) {
1833 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1836 lfSent = (outbuf[outlen - 1] == '\n');
1841 client_write(outbuf, outlen);
1842 lfSent = (outbuf[outlen - 1] == '\n');
1845 client_write(nl, nlen);
1849 /* If the format type on disk is 1 (fixed-format), then we want
1850 * everything to be output completely literally ... regardless of
1851 * what message transfer format is in use.
1853 void DumpFormatFixed(
1854 struct CtdlMessage *TheMessage,
1855 int mode, /* how would you like that message? */
1856 const char *nl, int nllen)
1864 mptr = TheMessage->cm_fields[eMesageText];
1866 if (mode == MT_MIME) {
1867 cprintf("Content-type: text/plain\n\n");
1871 while (ch = *mptr++, ch > 0) {
1875 if ((buflen > 250) && (!xlline)){
1879 while ((buflen > 0) &&
1880 (!isspace(buf[buflen])))
1886 mptr -= tbuflen - buflen;
1892 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1893 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1898 memcpy (&buf[buflen], nl, nllen);
1902 if (client_write(buf, buflen) == -1) {
1903 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1915 if (!IsEmptyStr(buf)) {
1916 cprintf("%s%s", buf, nl);
1922 * Get a message off disk. (returns om_* values found in msgbase.h)
1924 int CtdlOutputPreLoadedMsg(
1925 struct CtdlMessage *TheMessage,
1926 int mode, /* how would you like that message? */
1927 int headers_only, /* eschew the message body? */
1928 int do_proto, /* do Citadel protocol responses? */
1929 int crlf, /* Use CRLF newlines instead of LF? */
1930 int flags /* should the bessage be exported clean? */
1933 const char *nl; /* newline string */
1937 /* Buffers needed for RFC822 translation. These are all filled
1938 * using functions that are bounds-checked, and therefore we can
1939 * make them substantially smaller than SIZ.
1947 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1948 ((TheMessage == NULL) ? "NULL" : "not null"),
1949 mode, headers_only, do_proto, crlf
1952 strcpy(mid, "unknown");
1953 nl = (crlf ? "\r\n" : "\n");
1954 nlen = crlf ? 2 : 1;
1956 if (!CM_IsValidMsg(TheMessage)) {
1957 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1958 return(om_no_such_msg);
1961 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1962 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1964 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1965 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1968 /* Are we downloading a MIME component? */
1969 if (mode == MT_DOWNLOAD) {
1970 if (TheMessage->cm_format_type != FMT_RFC822) {
1972 cprintf("%d This is not a MIME message.\n",
1973 ERROR + ILLEGAL_VALUE);
1974 } else if (CC->download_fp != NULL) {
1975 if (do_proto) cprintf(
1976 "%d You already have a download open.\n",
1977 ERROR + RESOURCE_BUSY);
1979 /* Parse the message text component */
1980 mime_parser(CM_RANGE(TheMessage, eMesageText),
1981 *mime_download, NULL, NULL, NULL, 0);
1982 /* If there's no file open by this time, the requested
1983 * section wasn't found, so print an error
1985 if (CC->download_fp == NULL) {
1986 if (do_proto) cprintf(
1987 "%d Section %s not found.\n",
1988 ERROR + FILE_NOT_FOUND,
1989 CC->download_desired_section);
1992 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1995 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1996 // in a single server operation instead of opening a download file.
1997 if (mode == MT_SPEW_SECTION) {
1998 if (TheMessage->cm_format_type != FMT_RFC822) {
2000 cprintf("%d This is not a MIME message.\n",
2001 ERROR + ILLEGAL_VALUE);
2004 // Locate and parse the component specified by the caller
2006 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2008 // If section wasn't found, print an error
2011 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
2015 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2018 // now for the user-mode message reading loops
2019 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2021 // Does the caller want to skip the headers?
2022 if (headers_only == HEADERS_NONE) goto START_TEXT;
2024 // Tell the client which format type we're using.
2025 if ( (mode == MT_CITADEL) && (do_proto) ) {
2026 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2029 // nhdr=yes means that we're only displaying headers, no body
2030 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2031 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2034 cprintf("nhdr=yes\n");
2037 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2038 OutputCtdlMsgHeaders(TheMessage, do_proto);
2041 // begin header processing loop for RFC822 transfer format
2046 if (mode == MT_RFC822)
2047 OutputRFC822MsgHeaders(
2052 suser, sizeof(suser),
2053 luser, sizeof(luser),
2054 fuser, sizeof(fuser),
2055 snode, sizeof(snode)
2059 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2060 suser[i] = tolower(suser[i]);
2061 if (!isalnum(suser[i])) suser[i]='_';
2064 if (mode == MT_RFC822) {
2065 /* Construct a fun message id */
2066 cprintf("Message-ID: <%s", mid);
2067 if (strchr(mid, '@')==NULL) {
2068 cprintf("@%s", snode);
2072 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2073 cprintf("From: \"----\" <x@x.org>%s", nl);
2075 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2076 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2078 else if (!IsEmptyStr(fuser)) {
2079 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2082 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2085 /* Blank line signifying RFC822 end-of-headers */
2086 if (TheMessage->cm_format_type != FMT_RFC822) {
2091 // end header processing loop ... at this point, we're in the text
2093 if (headers_only == HEADERS_FAST) goto DONE;
2095 // Tell the client about the MIME parts in this message
2096 if (TheMessage->cm_format_type == FMT_RFC822) {
2097 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2098 memset(&ma, 0, sizeof(struct ma_info));
2099 mime_parser(CM_RANGE(TheMessage, eMesageText),
2100 (do_proto ? *list_this_part : NULL),
2101 (do_proto ? *list_this_pref : NULL),
2102 (do_proto ? *list_this_suff : NULL),
2105 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2106 Dump_RFC822HeadersBody(
2115 if (headers_only == HEADERS_ONLY) {
2119 // signify start of msg text
2120 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2121 if (do_proto) cprintf("text\n");
2124 if (TheMessage->cm_format_type == FMT_FIXED)
2127 mode, // how would you like that message?
2130 // If the message on disk is format 0 (Citadel vari-format), we
2131 // output using the formatter at 80 columns. This is the final output
2132 // form if the transfer format is RFC822, but if the transfer format
2133 // is Citadel proprietary, it'll still work, because the indentation
2134 // for new paragraphs is correct and the client will reformat the
2135 // message to the reader's screen width.
2137 if (TheMessage->cm_format_type == FMT_CITADEL) {
2138 if (mode == MT_MIME) {
2139 cprintf("Content-type: text/x-citadel-variformat\n\n");
2141 memfmout(TheMessage->cm_fields[eMesageText], nl);
2144 // If the message on disk is format 4 (MIME), we've gotta hand it
2145 // off to the MIME parser. The client has already been told that
2146 // this message is format 1 (fixed format), so the callback function
2147 // we use will display those parts as-is.
2149 if (TheMessage->cm_format_type == FMT_RFC822) {
2150 memset(&ma, 0, sizeof(struct ma_info));
2152 if (mode == MT_MIME) {
2153 ma.use_fo_hooks = 0;
2154 strcpy(ma.chosen_part, "1");
2155 ma.chosen_pref = 9999;
2156 ma.dont_decode = CC->msg4_dont_decode;
2157 mime_parser(CM_RANGE(TheMessage, eMesageText),
2158 *choose_preferred, *fixed_output_pre,
2159 *fixed_output_post, (void *)&ma, 1);
2160 mime_parser(CM_RANGE(TheMessage, eMesageText),
2161 *output_preferred, NULL, NULL, (void *)&ma, 1);
2164 ma.use_fo_hooks = 1;
2165 mime_parser(CM_RANGE(TheMessage, eMesageText),
2166 *fixed_output, *fixed_output_pre,
2167 *fixed_output_post, (void *)&ma, 0);
2172 DONE: /* now we're done */
2173 if (do_proto) cprintf("000\n");
2177 // Save one or more message pointers into a specified room
2178 // (Returns 0 for success, nonzero for failure)
2179 // roomname may be NULL to use the current room
2181 // Note that the 'supplied_msg' field may be set to NULL, in which case
2182 // the message will be fetched from disk, by number, if we need to perform
2183 // replication checks. This adds an additional database read, so if the
2184 // caller already has the message in memory then it should be supplied. (Obviously
2185 // this mode of operation only works if we're saving a single message.)
2187 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2188 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2191 char hold_rm[ROOMNAMELEN];
2192 struct cdbdata *cdbfr;
2195 long highest_msg = 0L;
2198 struct CtdlMessage *msg = NULL;
2200 long *msgs_to_be_merged = NULL;
2201 int num_msgs_to_be_merged = 0;
2204 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2205 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2208 strcpy(hold_rm, CC->room.QRname);
2211 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2212 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2213 if (num_newmsgs > 1) supplied_msg = NULL;
2215 /* Now the regular stuff */
2216 if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
2217 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2218 return(ERROR + ROOM_NOT_FOUND);
2221 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2222 num_msgs_to_be_merged = 0;
2223 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2225 /* Create a list of msgid's which were supplied by the caller, but do
2226 * not already exist in the target room. It is absolutely taboo to
2227 * have more than one reference to the same message in a room.
2229 for (i=0; i<num_newmsgs; ++i) {
2231 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2232 if (msglist[j] == newmsgidlist[i]) {
2237 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2241 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2244 * Now merge the new messages
2246 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2247 if (msglist == NULL) {
2248 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2249 free(msgs_to_be_merged);
2250 abort(); // FIXME FIXME FOOFOO
2251 return (ERROR + INTERNAL_ERROR);
2253 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2254 num_msgs += num_msgs_to_be_merged;
2256 /* Sort the message list, so all the msgid's are in order */
2257 num_msgs = sort_msglist(msglist, num_msgs);
2259 /* Determine the highest message number */
2260 highest_msg = msglist[num_msgs - 1];
2262 /* Write it back to disk. */
2263 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2265 /* Free up the memory we used. */
2268 /* Update the highest-message pointer and unlock the room. */
2269 CC->room.QRhighest = highest_msg;
2270 CtdlPutRoomLock(&CC->room);
2272 /* Perform replication checks if necessary */
2273 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2274 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2276 for (i=0; i<num_msgs_to_be_merged; ++i) {
2277 msgid = msgs_to_be_merged[i];
2279 if (supplied_msg != NULL) {
2283 msg = CtdlFetchMessage(msgid, 0);
2287 ReplicationChecks(msg);
2289 /* If the message has an Exclusive ID, index that... */
2290 if (!CM_IsEmpty(msg, eExclusiveID)) {
2291 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2294 /* Free up the memory we may have allocated */
2295 if (msg != supplied_msg) {
2304 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2307 /* Submit this room for processing by hooks */
2308 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2309 if (total_roomhook_errors) {
2310 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2313 /* Go back to the room we were in before we wandered here... */
2314 CtdlGetRoom(&CC->room, hold_rm);
2316 /* Bump the reference count for all messages which were merged */
2317 if (!suppress_refcount_adj) {
2318 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2321 /* Free up memory... */
2322 if (msgs_to_be_merged != NULL) {
2323 free(msgs_to_be_merged);
2326 /* Return success. */
2332 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2335 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
2336 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2341 * Message base operation to save a new message to the message store
2342 * (returns new message number)
2344 * This is the back end for CtdlSubmitMsg() and should not be directly
2345 * called by server-side modules.
2348 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2356 * If the message is big, set its body aside for storage elsewhere
2357 * and we hide the message body from the serializer
2359 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2361 holdM = msg->cm_fields[eMesageText];
2362 msg->cm_fields[eMesageText] = NULL;
2363 holdMLen = msg->cm_lengths[eMesageText];
2364 msg->cm_lengths[eMesageText] = 0;
2367 /* Serialize our data structure for storage in the database */
2368 CtdlSerializeMessage(&smr, msg);
2371 /* put the message body back into the message */
2372 msg->cm_fields[eMesageText] = holdM;
2373 msg->cm_lengths[eMesageText] = holdMLen;
2378 cprintf("%d Unable to serialize message\n",
2379 ERROR + INTERNAL_ERROR);
2382 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2388 /* Write our little bundle of joy into the message base */
2389 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2391 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2395 retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
2397 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2402 /* Free the memory we used for the serialized message */
2408 long send_message(struct CtdlMessage *msg) {
2414 /* Get a new message number */
2415 newmsgid = get_new_message_number();
2417 /* Generate an ID if we don't have one already */
2418 if (CM_IsEmpty(msg, emessageId)) {
2419 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2420 (long unsigned int) time(NULL),
2421 (long unsigned int) newmsgid,
2422 CtdlGetConfigStr("c_fqdn")
2425 CM_SetField(msg, emessageId, msgidbuf);
2428 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2434 /* Return the *local* message ID to the caller
2435 * (even if we're storing an incoming network message)
2442 * Serialize a struct CtdlMessage into the format used on disk.
2444 * This function loads up a "struct ser_ret" (defined in server.h) which
2445 * contains the length of the serialized message and a pointer to the
2446 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2448 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2449 struct CtdlMessage *msg) /* unserialized msg */
2455 * Check for valid message format
2457 if (CM_IsValidMsg(msg) == 0) {
2458 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2465 for (i=0; i < NDiskFields; ++i)
2466 if (msg->cm_fields[FieldOrder[i]] != NULL)
2467 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2469 ret->ser = malloc(ret->len);
2470 if (ret->ser == NULL) {
2471 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2478 ret->ser[1] = msg->cm_anon_type;
2479 ret->ser[2] = msg->cm_format_type;
2482 for (i=0; i < NDiskFields; ++i) {
2483 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2484 ret->ser[wlen++] = (char)FieldOrder[i];
2486 memcpy(&ret->ser[wlen],
2487 msg->cm_fields[FieldOrder[i]],
2488 msg->cm_lengths[FieldOrder[i]] + 1);
2490 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2494 if (ret->len != wlen) {
2495 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2503 * Check to see if any messages already exist in the current room which
2504 * carry the same Exclusive ID as this one. If any are found, delete them.
2506 void ReplicationChecks(struct CtdlMessage *msg) {
2507 long old_msgnum = (-1L);
2509 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2511 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2513 /* No exclusive id? Don't do anything. */
2514 if (msg == NULL) return;
2515 if (CM_IsEmpty(msg, eExclusiveID)) return;
2517 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2518 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2520 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2521 if (old_msgnum > 0L) {
2522 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2523 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2529 * Save a message to disk and submit it into the delivery system.
2531 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2532 struct recptypes *recps, /* recipients (if mail) */
2533 const char *force /* force a particular room? */
2535 char hold_rm[ROOMNAMELEN];
2536 char actual_rm[ROOMNAMELEN];
2537 char force_room[ROOMNAMELEN];
2538 char content_type[SIZ]; /* We have to learn this */
2539 char recipient[SIZ];
2540 char bounce_to[1024];
2543 const char *mptr = NULL;
2544 struct ctdluser userbuf;
2546 struct MetaData smi;
2547 char *collected_addresses = NULL;
2548 struct addresses_to_be_filed *aptr = NULL;
2549 StrBuf *saved_rfc822_version = NULL;
2550 int qualified_for_journaling = 0;
2552 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2553 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2555 /* If this message has no timestamp, we take the liberty of
2556 * giving it one, right now.
2558 if (CM_IsEmpty(msg, eTimestamp)) {
2559 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2562 /* If this message has no path, we generate one.
2564 if (CM_IsEmpty(msg, eMessagePath)) {
2565 if (!CM_IsEmpty(msg, eAuthor)) {
2566 CM_CopyField(msg, eMessagePath, eAuthor);
2567 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2568 if (isspace(msg->cm_fields[eMessagePath][a])) {
2569 msg->cm_fields[eMessagePath][a] = ' ';
2574 CM_SetField(msg, eMessagePath, "unknown");
2578 if (force == NULL) {
2579 force_room[0] = '\0';
2582 strcpy(force_room, force);
2585 /* Learn about what's inside, because it's what's inside that counts */
2586 if (CM_IsEmpty(msg, eMesageText)) {
2587 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2591 switch (msg->cm_format_type) {
2593 strcpy(content_type, "text/x-citadel-variformat");
2596 strcpy(content_type, "text/plain");
2599 strcpy(content_type, "text/plain");
2600 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2603 safestrncpy(content_type, &mptr[13], sizeof content_type);
2604 string_trim(content_type);
2605 aptr = content_type;
2606 while (!IsEmptyStr(aptr)) {
2618 /* Goto the correct room */
2619 room = (recps) ? CC->room.QRname : SENTITEMS;
2620 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2621 strcpy(hold_rm, CC->room.QRname);
2622 strcpy(actual_rm, CC->room.QRname);
2623 if (recps != NULL) {
2624 strcpy(actual_rm, SENTITEMS);
2627 /* If the user is a twit, move to the twit room for posting */
2629 if (CC->user.axlevel == AxProbU) {
2630 strcpy(hold_rm, actual_rm);
2631 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2632 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2636 /* ...or if this message is destined for Aide> then go there. */
2637 if (!IsEmptyStr(force_room)) {
2638 strcpy(actual_rm, force_room);
2641 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2642 if (strcasecmp(actual_rm, CC->room.QRname)) {
2643 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2647 * If this message has no O (room) field, generate one.
2649 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2650 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2653 /* Perform "before save" hooks (aborting if any return nonzero) */
2654 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2655 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2658 * If this message has an Exclusive ID, and the room is replication
2659 * checking enabled, then do replication checks.
2661 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2662 ReplicationChecks(msg);
2665 /* Save it to disk */
2666 syslog(LOG_DEBUG, "msgbase: saving to disk");
2667 newmsgid = send_message(msg);
2668 if (newmsgid <= 0L) return(-5);
2670 /* Write a supplemental message info record. This doesn't have to
2671 * be a critical section because nobody else knows about this message
2674 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2675 memset(&smi, 0, sizeof(struct MetaData));
2676 smi.meta_msgnum = newmsgid;
2677 smi.meta_refcount = 0;
2678 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2681 * Measure how big this message will be when rendered as RFC822.
2682 * We do this for two reasons:
2683 * 1. We need the RFC822 length for the new metadata record, so the
2684 * POP and IMAP services don't have to calculate message lengths
2685 * while the user is waiting (multiplied by potentially hundreds
2686 * or thousands of messages).
2687 * 2. If journaling is enabled, we will need an RFC822 version of the
2688 * message to attach to the journalized copy.
2690 if (CC->redirect_buffer != NULL) {
2691 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2692 exit(CTDLEXIT_REDIRECT);
2694 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2695 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2696 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2697 saved_rfc822_version = CC->redirect_buffer;
2698 CC->redirect_buffer = NULL;
2702 /* Now figure out where to store the pointers */
2703 syslog(LOG_DEBUG, "msgbase: storing pointers");
2705 /* If this is being done by the networker delivering a private
2706 * message, we want to BYPASS saving the sender's copy (because there
2707 * is no local sender; it would otherwise go to the Trashcan).
2709 if ((!CC->internal_pgm) || (recps == NULL)) {
2710 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2711 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2712 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2716 /* For internet mail, drop a copy in the outbound queue room */
2717 if ((recps != NULL) && (recps->num_internet > 0)) {
2718 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2721 /* If other rooms are specified, drop them there too. */
2722 if ((recps != NULL) && (recps->num_room > 0)) {
2723 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2724 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2725 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2726 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2730 /* Decide where bounces need to be delivered */
2731 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2732 if (CC->logged_in) {
2733 strcpy(bounce_to, CC->user.fullname);
2735 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2736 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2738 recps->bounce_to = bounce_to;
2741 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2743 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2744 if ((recps != NULL) && (recps->num_local > 0)) {
2748 pch = recps->recp_local;
2749 recps->recp_local = recipient;
2750 ntokens = num_tokens(pch, '|');
2751 for (i=0; i<ntokens; ++i) {
2752 extract_token(recipient, pch, i, '|', sizeof recipient);
2753 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2754 if (CtdlGetUser(&userbuf, recipient) == 0) {
2755 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2756 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2757 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2758 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2761 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2762 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2765 recps->recp_local = pch;
2768 /* Perform "after save" hooks */
2769 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2771 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2772 CM_FlushField(msg, eVltMsgNum);
2774 /* Go back to the room we started from */
2775 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2776 if (strcasecmp(hold_rm, CC->room.QRname)) {
2777 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2781 * Any addresses to harvest for someone's address book?
2783 if ( (CC->logged_in) && (recps != NULL) ) {
2784 collected_addresses = harvest_collected_addresses(msg);
2787 if (collected_addresses != NULL) {
2788 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2789 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2790 aptr->roomname = strdup(actual_rm);
2791 aptr->collected_addresses = collected_addresses;
2792 begin_critical_section(S_ATBF);
2795 end_critical_section(S_ATBF);
2799 * Determine whether this message qualifies for journaling.
2801 if (!CM_IsEmpty(msg, eJournal)) {
2802 qualified_for_journaling = 0;
2805 if (recps == NULL) {
2806 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2808 else if (recps->num_local + recps->num_internet > 0) {
2809 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2812 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2817 * Do we have to perform journaling? If so, hand off the saved
2818 * RFC822 version will be handed off to the journaler for background
2819 * submit. Otherwise, we have to free the memory ourselves.
2821 if (saved_rfc822_version != NULL) {
2822 if (qualified_for_journaling) {
2823 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2826 FreeStrBuf(&saved_rfc822_version);
2830 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2831 recps->bounce_to = NULL;
2839 * Convenience function for generating small administrative messages.
2841 long quickie_message(char *from,
2849 struct CtdlMessage *msg;
2850 struct recptypes *recp = NULL;
2852 msg = malloc(sizeof(struct CtdlMessage));
2853 memset(msg, 0, sizeof(struct CtdlMessage));
2854 msg->cm_magic = CTDLMESSAGE_MAGIC;
2855 msg->cm_anon_type = MES_NORMAL;
2856 msg->cm_format_type = format_type;
2858 if (!IsEmptyStr(from)) {
2859 CM_SetField(msg, eAuthor, from);
2861 else if (!IsEmptyStr(fromaddr)) {
2863 CM_SetField(msg, eAuthor, fromaddr);
2864 pAt = strchr(msg->cm_fields[eAuthor], '@');
2866 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2870 msg->cm_fields[eAuthor] = strdup("Citadel");
2873 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr);
2874 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
2875 if (!IsEmptyStr(to)) {
2876 CM_SetField(msg, eRecipient, to);
2877 recp = validate_recipients(to, NULL, 0);
2879 if (!IsEmptyStr(subject)) {
2880 CM_SetField(msg, eMsgSubject, subject);
2882 if (!IsEmptyStr(text)) {
2883 CM_SetField(msg, eMesageText, text);
2886 long msgnum = CtdlSubmitMsg(msg, recp, room);
2888 if (recp != NULL) free_recipients(recp);
2894 * Back end function used by CtdlMakeMessage() and similar functions
2896 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2898 size_t maxlen, // maximum message length
2899 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2900 int crlf // CRLF newlines instead of LF
2908 LineBuf = NewStrBufPlain(NULL, SIZ);
2909 if (exist == NULL) {
2910 Message = NewStrBufPlain(NULL, 4 * SIZ);
2913 Message = NewStrBufDup(exist);
2916 /* Do we need to change leading ".." to "." for SMTP escaping? */
2917 if ((tlen == 1) && (*terminator == '.')) {
2921 /* read in the lines of message text one by one */
2923 if (CtdlClientGetLine(LineBuf) < 0) {
2926 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2929 if ( (!flushing) && (!finished) ) {
2931 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2934 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2937 /* Unescape SMTP-style input of two dots at the beginning of the line */
2938 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2939 StrBufCutLeft(LineBuf, 1);
2941 StrBufAppendBuf(Message, LineBuf, 0);
2944 /* if we've hit the max msg length, flush the rest */
2945 if (StrLength(Message) >= maxlen) {
2949 } while (!finished);
2950 FreeStrBuf(&LineBuf);
2953 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2960 // Back end function used by CtdlMakeMessage() and similar functions
2961 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2963 size_t maxlen, // maximum message length
2964 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2965 int crlf // CRLF newlines instead of LF
2969 Message = CtdlReadMessageBodyBuf(terminator,
2975 if (Message == NULL) {
2979 return SmashStrBuf(&Message);
2984 struct CtdlMessage *CtdlMakeMessage(
2985 struct ctdluser *author, /* author's user structure */
2986 char *recipient, /* NULL if it's not mail */
2987 char *recp_cc, /* NULL if it's not mail */
2988 char *room, /* room where it's going */
2989 int type, /* see MES_ types in header file */
2990 int format_type, /* variformat, plain text, MIME... */
2991 char *fake_name, /* who we're masquerading as */
2992 char *my_email, /* which of my email addresses to use (empty is ok) */
2993 char *subject, /* Subject (optional) */
2994 char *supplied_euid, /* ...or NULL if this is irrelevant */
2995 char *preformatted_text, /* ...or NULL to read text from client */
2996 char *references /* Thread references */
2998 return CtdlMakeMessageLen(
2999 author, /* author's user structure */
3000 recipient, /* NULL if it's not mail */
3001 (recipient)?strlen(recipient) : 0,
3002 recp_cc, /* NULL if it's not mail */
3003 (recp_cc)?strlen(recp_cc): 0,
3004 room, /* room where it's going */
3005 (room)?strlen(room): 0,
3006 type, /* see MES_ types in header file */
3007 format_type, /* variformat, plain text, MIME... */
3008 fake_name, /* who we're masquerading as */
3009 (fake_name)?strlen(fake_name): 0,
3010 my_email, /* which of my email addresses to use (empty is ok) */
3011 (my_email)?strlen(my_email): 0,
3012 subject, /* Subject (optional) */
3013 (subject)?strlen(subject): 0,
3014 supplied_euid, /* ...or NULL if this is irrelevant */
3015 (supplied_euid)?strlen(supplied_euid):0,
3016 preformatted_text, /* ...or NULL to read text from client */
3017 (preformatted_text)?strlen(preformatted_text) : 0,
3018 references, /* Thread references */
3019 (references)?strlen(references):0);
3025 * Build a binary message to be saved on disk.
3026 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3027 * will become part of the message. This means you are no longer
3028 * responsible for managing that memory -- it will be freed along with
3029 * the rest of the fields when CM_Free() is called.)
3031 struct CtdlMessage *CtdlMakeMessageLen(
3032 struct ctdluser *author, /* author's user structure */
3033 char *recipient, /* NULL if it's not mail */
3035 char *recp_cc, /* NULL if it's not mail */
3037 char *room, /* room where it's going */
3039 int type, /* see MES_ types in header file */
3040 int format_type, /* variformat, plain text, MIME... */
3041 char *fake_name, /* who we're masquerading as */
3043 char *my_email, /* which of my email addresses to use (empty is ok) */
3045 char *subject, /* Subject (optional) */
3047 char *supplied_euid, /* ...or NULL if this is irrelevant */
3049 char *preformatted_text, /* ...or NULL to read text from client */
3051 char *references, /* Thread references */
3056 struct CtdlMessage *msg;
3058 StrBuf *FakeEncAuthor = NULL;
3060 msg = malloc(sizeof(struct CtdlMessage));
3061 memset(msg, 0, sizeof(struct CtdlMessage));
3062 msg->cm_magic = CTDLMESSAGE_MAGIC;
3063 msg->cm_anon_type = type;
3064 msg->cm_format_type = format_type;
3066 if (recipient != NULL) rcplen = string_trim(recipient);
3067 if (recp_cc != NULL) cclen = string_trim(recp_cc);
3069 /* Path or Return-Path */
3071 CM_SetField(msg, eMessagePath, my_email);
3073 else if (!IsEmptyStr(author->fullname)) {
3074 CM_SetField(msg, eMessagePath, author->fullname);
3076 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3078 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3079 CM_SetField(msg, eTimestamp, buf);
3082 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3085 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3087 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3088 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3089 FreeStrBuf(&FakeAuthor);
3091 if (!!IsEmptyStr(CC->room.QRname)) {
3092 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3093 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11]);
3096 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
3101 CM_SetField(msg, eRecipient, recipient);
3104 CM_SetField(msg, eCarbonCopY, recp_cc);
3108 CM_SetField(msg, erFc822Addr, my_email);
3110 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3111 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
3114 if (subject != NULL) {
3116 length = string_trim(subject);
3122 while ((subject[i] != '\0') &&
3123 (IsAscii = isascii(subject[i]) != 0 ))
3126 CM_SetField(msg, eMsgSubject, subject);
3127 else /* ok, we've got utf8 in the string. */
3130 rfc2047Subj = rfc2047encode(subject, length);
3131 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3138 CM_SetField(msg, eExclusiveID, supplied_euid);
3142 CM_SetField(msg, eWeferences, references);
3145 if (preformatted_text != NULL) {
3146 CM_SetField(msg, eMesageText, preformatted_text);
3150 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3151 if (MsgBody != NULL) {
3152 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3161 * API function to delete messages which match a set of criteria
3162 * (returns the actual number of messages deleted)
3164 int CtdlDeleteMessages(const char *room_name, // which room
3165 long *dmsgnums, // array of msg numbers to be deleted
3166 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3167 char *content_type // or "" for any. regular expressions expected.
3169 struct ctdlroom qrbuf;
3170 struct cdbdata *cdbfr;
3171 long *msglist = NULL;
3172 long *dellist = NULL;
3175 int num_deleted = 0;
3177 struct MetaData smi;
3180 int need_to_free_re = 0;
3182 if (content_type) if (!IsEmptyStr(content_type)) {
3183 regcomp(&re, content_type, 0);
3184 need_to_free_re = 1;
3186 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3188 /* get room record, obtaining a lock... */
3189 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3190 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3191 if (need_to_free_re) regfree(&re);
3192 return(0); /* room not found */
3195 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3197 dellist = malloc(num_msgs * sizeof(long));
3198 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3199 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3200 int have_more_del = 1;
3202 num_msgs = sort_msglist(msglist, num_msgs);
3203 if (num_dmsgnums > 1) {
3204 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3208 while ((i < num_msgs) && (have_more_del)) {
3211 /* Set/clear a bit for each criterion */
3213 /* 0 messages in the list or a null list means that we are
3214 * interested in deleting any messages which meet the other criteria.
3217 delete_this |= 0x01;
3220 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3225 if (msglist[i] == dmsgnums[j]) {
3226 delete_this |= 0x01;
3229 have_more_del = (j < num_dmsgnums);
3232 if (have_contenttype) {
3233 GetMetaData(&smi, msglist[i]);
3234 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3235 delete_this |= 0x02;
3238 delete_this |= 0x02;
3241 /* Delete message only if all bits are set */
3242 if (delete_this == 0x03) {
3243 dellist[num_deleted++] = msglist[i];
3249 num_msgs = sort_msglist(msglist, num_msgs);
3250 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3253 qrbuf.QRhighest = msglist[num_msgs - 1];
3256 qrbuf.QRhighest = 0;
3259 CtdlPutRoomLock(&qrbuf);
3261 /* Go through the messages we pulled out of the index, and decrement
3262 * their reference counts by 1. If this is the only room the message
3263 * was in, the reference count will reach zero and the message will
3264 * automatically be deleted from the database. We do this in a
3265 * separate pass because there might be plug-in hooks getting called,
3266 * and we don't want that happening during an S_ROOMS critical
3270 for (i=0; i<num_deleted; ++i) {
3271 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3273 AdjRefCountList(dellist, num_deleted, -1);
3275 /* Now free the memory we used, and go away. */
3276 if (msglist != NULL) free(msglist);
3277 if (dellist != NULL) free(dellist);
3278 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3279 if (need_to_free_re) regfree(&re);
3280 return (num_deleted);
3285 * GetMetaData() - Get the supplementary record for a message
3287 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3288 struct cdbdata cdbsmi;
3291 memset(smibuf, 0, sizeof(struct MetaData));
3292 smibuf->meta_msgnum = msgnum;
3293 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3295 /* Use the negative of the message number for its supp record index */
3296 TheIndex = (0L - msgnum);
3298 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3299 if (cdbsmi.ptr == NULL) {
3300 return; /* record not found; leave it alone */
3302 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3308 * PutMetaData() - (re)write supplementary record for a message
3310 void PutMetaData(struct MetaData *smibuf)
3314 /* Use the negative of the message number for the metadata db index */
3315 TheIndex = (0L - smibuf->meta_msgnum);
3317 cdb_store(CDB_MSGMAIN,
3318 &TheIndex, (int)sizeof(long),
3319 smibuf, (int)sizeof(struct MetaData)
3324 // Convenience function to process a big block of AdjRefCount() operations
3325 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3328 for (i = 0; i < nmsg; i++) {
3329 AdjRefCount(msgnum[i], incr);
3334 // AdjRefCount - adjust the reference count for a message.
3335 // We need to delete from disk any message whose reference count reaches zero.
3336 void AdjRefCount(long msgnum, int incr) {
3337 struct MetaData smi;
3340 // This is a *tight* critical section; please keep it that way, as
3341 // it may get called while nested in other critical sections.
3342 // Complicating this any further will surely cause deadlock!
3343 begin_critical_section(S_SUPPMSGMAIN);
3344 GetMetaData(&smi, msgnum);
3345 smi.meta_refcount += incr;
3347 end_critical_section(S_SUPPMSGMAIN);
3348 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3350 // If the reference count is now zero, delete both the message and its metadata record.
3351 if (smi.meta_refcount == 0) {
3352 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3354 // Call delete hooks with NULL room to show it has gone altogether
3355 PerformDeleteHooks(NULL, msgnum);
3357 // Remove from message base
3359 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3360 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3362 // Remove metadata record
3363 delnum = (0L - msgnum);
3364 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3369 // Write a generic object to this room
3370 // Returns the message number of the written object, in case you need it.
3371 long CtdlWriteObject(char *req_room, // Room to stuff it in
3372 char *content_type, // MIME type of this object
3373 char *raw_message, // Data to be written
3374 off_t raw_length, // Size of raw_message
3375 struct ctdluser *is_mailbox, // Mailbox room?
3376 int is_binary, // Is encoding necessary?
3377 unsigned int flags // Internal save flags
3379 struct ctdlroom qrbuf;
3380 char roomname[ROOMNAMELEN];
3381 struct CtdlMessage *msg;
3382 StrBuf *encoded_message = NULL;
3384 if (is_mailbox != NULL) {
3385 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3388 safestrncpy(roomname, req_room, sizeof(roomname));
3391 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3394 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3397 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3400 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3401 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3402 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3405 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3408 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3412 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3415 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3418 syslog(LOG_DEBUG, "msgbase: allocating");
3419 msg = malloc(sizeof(struct CtdlMessage));
3420 memset(msg, 0, sizeof(struct CtdlMessage));
3421 msg->cm_magic = CTDLMESSAGE_MAGIC;
3422 msg->cm_anon_type = MES_NORMAL;
3423 msg->cm_format_type = 4;
3424 CM_SetField(msg, eAuthor, CC->user.fullname);
3425 CM_SetField(msg, eOriginalRoom, req_room);
3426 msg->cm_flags = flags;
3428 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3430 /* Create the requested room if we have to. */
3431 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3432 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3435 /* Now write the data */
3436 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3442 /************************************************************************/
3443 /* MODULE INITIALIZATION */
3444 /************************************************************************/
3446 char *ctdl_module_init_msgbase(void) {
3448 FillMsgKeyLookupTable();
3451 /* return our module id for the log */