1 // Implements the message store.
3 // Copyright (c) 1987-2023 by the citadel.org team
5 // This program is open source software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License version 3.
14 #include <libcitadel.h>
15 #include "ctdl_module.h"
16 #include "citserver.h"
19 #include "clientsocket.h"
23 #include "internet_addressing.h"
24 #include "euidindex.h"
26 #include "journaling.h"
28 struct addresses_to_be_filed *atbf = NULL;
30 // These are the four-character field headers we use when outputting
31 // messages in Citadel format (as opposed to RFC822 format).
33 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
34 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
35 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
36 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
37 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
38 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
40 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
42 "from", // A -> eAuthor
43 NULL, // B -> eBig_message
44 NULL, // C (formerly used as eRemoteRoom)
45 NULL, // D (formerly used as eDestination)
46 "exti", // E -> eXclusivID
47 "rfca", // F -> erFc822Addr
49 "hnod", // H (formerly used as eHumanNode)
50 "msgn", // I -> emessageId
51 "jrnl", // J -> eJournal
52 "rep2", // K -> eReplyTo
53 "list", // L -> eListID
54 "text", // M -> eMesageText
55 NULL, // N (formerly used as eNodename)
56 "room", // O -> eOriginalRoom
57 "path", // P -> eMessagePath
59 "rcpt", // R -> eRecipient
60 NULL, // S (formerly used as eSpecialField)
61 "time", // T -> eTimestamp
62 "subj", // U -> eMsgSubject
63 "nvto", // V -> eenVelopeTo
64 "wefw", // W -> eWeferences
66 "cccc", // Y -> eCarbonCopY
71 HashList *msgKeyLookup = NULL;
73 int GetFieldFromMnemonic(eMsgField *f, const char* c) {
75 if (GetHash(msgKeyLookup, c, 4, &v)) {
82 void FillMsgKeyLookupTable(void) {
85 msgKeyLookup = NewHash (1, FourHash);
87 for (i=0; i < 91; i++) {
88 if (msgkeys[i] != NULL) {
89 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
95 eMsgField FieldOrder[] = {
96 /* Important fields */
104 /* Semi-important fields */
109 /* G is not used yet */
112 /* Q is not used yet */
114 /* X is not used yet */
115 /* Z is not used yet */
122 /* Message text (MUST be last) */
124 /* Not saved to disk:
129 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
132 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
133 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
137 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf) {
138 if (Msg->cm_fields[which] != NULL) {
139 free(Msg->cm_fields[which]);
141 Msg->cm_fields[which] = strdup(buf);
142 Msg->cm_lengths[which] = strlen(buf);
146 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
149 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
150 CM_SetField(Msg, which, buf);
154 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
155 if (Msg->cm_fields[WhichToCut] == NULL)
158 if (Msg->cm_lengths[WhichToCut] > maxlen)
160 Msg->cm_fields[WhichToCut][maxlen] = '\0';
161 Msg->cm_lengths[WhichToCut] = maxlen;
166 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
167 if (Msg->cm_fields[which] != NULL)
168 free (Msg->cm_fields[which]);
169 Msg->cm_fields[which] = NULL;
170 Msg->cm_lengths[which] = 0;
174 void CM_Flush(struct CtdlMessage *Msg) {
177 if (CM_IsValidMsg(Msg) == 0) {
181 for (i = 0; i < 256; ++i) {
182 CM_FlushField(Msg, i);
187 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
189 if (Msg->cm_fields[WhichToPutTo] != NULL) {
190 free (Msg->cm_fields[WhichToPutTo]);
193 if (Msg->cm_fields[WhichtToCopy] != NULL) {
194 len = Msg->cm_lengths[WhichtToCopy];
195 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
196 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
197 Msg->cm_fields[WhichToPutTo][len] = '\0';
198 Msg->cm_lengths[WhichToPutTo] = len;
201 Msg->cm_fields[WhichToPutTo] = NULL;
202 Msg->cm_lengths[WhichToPutTo] = 0;
207 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
208 if (Msg->cm_fields[which] != NULL) {
213 oldmsgsize = Msg->cm_lengths[which] + 1;
214 newmsgsize = length + oldmsgsize;
216 new = malloc(newmsgsize);
217 memcpy(new, buf, length);
218 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
219 free(Msg->cm_fields[which]);
220 Msg->cm_fields[which] = new;
221 Msg->cm_lengths[which] = newmsgsize - 1;
224 Msg->cm_fields[which] = malloc(length + 1);
225 memcpy(Msg->cm_fields[which], buf, length);
226 Msg->cm_fields[which][length] = '\0';
227 Msg->cm_lengths[which] = length;
232 // This is like CM_SetField() except the caller is transferring ownership of the supplied memory to the message
233 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
234 if (Msg->cm_fields[which] != NULL) {
235 free (Msg->cm_fields[which]);
238 Msg->cm_fields[which] = *buf;
240 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
241 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
244 Msg->cm_lengths[which] = length;
249 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
250 if (Msg->cm_fields[which] != NULL) {
251 free (Msg->cm_fields[which]);
254 Msg->cm_lengths[which] = StrLength(*buf);
255 Msg->cm_fields[which] = SmashStrBuf(buf);
259 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
260 if (Msg->cm_fields[which] != NULL) {
261 *retlen = Msg->cm_lengths[which];
262 *ret = Msg->cm_fields[which];
263 Msg->cm_fields[which] = NULL;
264 Msg->cm_lengths[which] = 0;
273 // Returns 1 if the supplied pointer points to a valid Citadel message.
274 // If the pointer is NULL or the magic number check fails, returns 0.
275 int CM_IsValidMsg(struct CtdlMessage *msg) {
279 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
280 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
287 void CM_FreeContents(struct CtdlMessage *msg) {
290 for (i = 0; i < 256; ++i)
291 if (msg->cm_fields[i] != NULL) {
292 free(msg->cm_fields[i]);
293 msg->cm_lengths[i] = 0;
296 msg->cm_magic = 0; // just in case
300 // 'Destructor' for struct CtdlMessage
301 void CM_Free(struct CtdlMessage *msg) {
302 if (CM_IsValidMsg(msg) == 0) {
303 if (msg != NULL) free (msg);
306 CM_FreeContents(msg);
311 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
313 len = OrgMsg->cm_lengths[i];
314 NewMsg->cm_fields[i] = malloc(len + 1);
315 if (NewMsg->cm_fields[i] == NULL) {
318 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
319 NewMsg->cm_fields[i][len] = '\0';
320 NewMsg->cm_lengths[i] = len;
325 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
327 struct CtdlMessage *NewMsg;
329 if (CM_IsValidMsg(OrgMsg) == 0) {
332 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
333 if (NewMsg == NULL) {
337 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
339 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
341 for (i = 0; i < 256; ++i) {
342 if (OrgMsg->cm_fields[i] != NULL) {
343 if (!CM_DupField(i, OrgMsg, NewMsg)) {
354 // Determine if a given message matches the fields in a message template.
355 // Return 0 for a successful match.
356 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
359 // If there aren't any fields in the template, all messages will match.
360 if (template == NULL) return(0);
362 // Null messages are bogus.
363 if (msg == NULL) return(1);
365 for (i='A'; i<='Z'; ++i) {
366 if (template->cm_fields[i] != NULL) {
367 if (msg->cm_fields[i] == NULL) {
368 // Considered equal if temmplate is empty string
369 if (IsEmptyStr(template->cm_fields[i])) continue;
372 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
373 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
378 // All compares succeeded: we have a match!
383 // Retrieve the "seen" message list for the current room.
384 void CtdlGetSeen(char *buf, int which_set) {
387 // Learn about the user and room in question
388 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
390 if (which_set == ctdlsetseen_seen) {
391 safestrncpy(buf, vbuf.v_seen, SIZ);
393 if (which_set == ctdlsetseen_answered) {
394 safestrncpy(buf, vbuf.v_answered, SIZ);
399 // Manipulate the "seen msgs" string (or other message set strings)
400 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
401 int target_setting, int which_set,
402 struct ctdluser *which_user, struct ctdlroom *which_room) {
416 char *is_set; // actually an array of booleans
418 // Don't bother doing *anything* if we were passed a list of zero messages
419 if (num_target_msgnums < 1) {
423 // If no room was specified, we go with the current room.
425 which_room = &CC->room;
428 // If no user was specified, we go with the current user.
430 which_user = &CC->user;
433 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
434 num_target_msgnums, target_msgnums[0],
435 (target_setting ? "SET" : "CLEAR"),
439 // Learn about the user and room in question
440 CtdlGetRelationship(&vbuf, which_user, which_room);
442 // Load the message list
443 num_msgs = CtdlFetchMsgList(which_room->QRnumber, &msglist);
449 is_set = malloc(num_msgs * sizeof(char));
450 memset(is_set, 0, (num_msgs * sizeof(char)) );
452 // Decide which message set we're manipulating
454 case ctdlsetseen_seen:
455 vset = NewStrBufPlain(vbuf.v_seen, -1);
457 case ctdlsetseen_answered:
458 vset = NewStrBufPlain(vbuf.v_answered, -1);
464 // Translate the existing sequence set into an array of booleans
465 setstr = NewStrBuf();
469 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
471 StrBufExtract_token(lostr, setstr, 0, ':');
472 if (StrBufNum_tokens(setstr, ':') >= 2) {
473 StrBufExtract_token(histr, setstr, 1, ':');
477 StrBufAppendBuf(histr, lostr, 0);
480 if (!strcmp(ChrPtr(histr), "*")) {
487 for (i = 0; i < num_msgs; ++i) {
488 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
497 // Now translate the array of booleans back into a sequence set
503 for (i=0; i<num_msgs; ++i) {
507 for (k=0; k<num_target_msgnums; ++k) {
508 if (msglist[i] == target_msgnums[k]) {
509 is_seen = target_setting;
513 if ((was_seen == 0) && (is_seen == 1)) {
516 else if ((was_seen == 1) && (is_seen == 0)) {
519 if (StrLength(vset) > 0) {
520 StrBufAppendBufPlain(vset, HKEY(","), 0);
523 StrBufAppendPrintf(vset, "%ld", hi);
526 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
530 if ((is_seen) && (i == num_msgs - 1)) {
531 if (StrLength(vset) > 0) {
532 StrBufAppendBufPlain(vset, HKEY(","), 0);
534 if ((i==0) || (was_seen == 0)) {
535 StrBufAppendPrintf(vset, "%ld", msglist[i]);
538 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
545 // We will have to stuff this string back into a 4096 byte buffer, so if it's
546 // larger than that now, truncate it by removing tokens from the beginning.
547 // The limit of 100 iterations is there to prevent an infinite loop in case
548 // something unexpected happens.
549 int number_of_truncations = 0;
550 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
551 StrBufRemove_token(vset, 0, ',');
552 ++number_of_truncations;
555 // If we're truncating the sequence set of messages marked with the 'seen' flag,
556 // we want the earliest messages (the truncated ones) to be marked, not unmarked.
557 // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
558 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
560 first_tok = NewStrBuf();
561 StrBufExtract_token(first_tok, vset, 0, ',');
562 StrBufRemove_token(vset, 0, ',');
564 if (StrBufNum_tokens(first_tok, ':') > 1) {
565 StrBufRemove_token(first_tok, 0, ':');
569 new_set = NewStrBuf();
570 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
571 StrBufAppendBuf(new_set, first_tok, 0);
572 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
573 StrBufAppendBuf(new_set, vset, 0);
576 FreeStrBuf(&first_tok);
580 // Decide which message set we're manipulating. Zero the buffers so they compress well.
582 case ctdlsetseen_seen:
583 memset(vbuf.v_seen, 0, sizeof vbuf.v_seen);
584 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
586 case ctdlsetseen_answered:
587 memset(vbuf.v_answered, 0, sizeof vbuf.v_seen);
588 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
594 CtdlSetRelationship(&vbuf, which_user, which_room);
599 // API function to perform an operation for each qualifying message in the
600 // current room. (Returns the number of messages processed.)
601 int CtdlForEachMessage(int mode, long ref, char *search_string,
603 struct CtdlMessage *compare,
604 ForEachMsgCallback CallBack,
609 long *msglist = NULL;
611 int num_processed = 0;
614 struct CtdlMessage *msg = NULL;
617 int printed_lastold = 0;
618 int num_search_msgs = 0;
619 long *search_msgs = NULL;
621 int need_to_free_re = 0;
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) {
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 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
724 if (num_search_msgs > 0) {
728 orig_num_msgs = num_msgs;
730 for (i=0; i<orig_num_msgs; ++i) {
731 for (j=0; j<num_search_msgs; ++j) {
732 if (msglist[i] == search_msgs[j]) {
733 msglist[num_msgs++] = msglist[i];
739 num_msgs = 0; /* No messages qualify */
741 if (search_msgs != NULL) free(search_msgs);
743 /* Now that we've purged messages which don't contain the search
744 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
751 * Now iterate through the message list, according to the
752 * criteria supplied by the caller.
755 for (a = 0; a < num_msgs; ++a) {
756 if (server_shutting_down) {
757 if (need_to_free_re) regfree(&re);
759 return num_processed;
761 thismsg = msglist[a];
762 if (mode == MSGS_ALL) {
766 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
767 if (is_seen) lastold = thismsg;
773 || ((mode == MSGS_OLD) && (is_seen))
774 || ((mode == MSGS_NEW) && (!is_seen))
775 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
776 || ((mode == MSGS_FIRST) && (a < ref))
777 || ((mode == MSGS_GT) && (thismsg > ref))
778 || ((mode == MSGS_LT) && (thismsg < ref))
779 || ((mode == MSGS_EQ) && (thismsg == ref))
782 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
784 CallBack(lastold, userdata);
790 CallBack(thismsg, userdata);
795 if (need_to_free_re) regfree(&re);
798 * We cache the most recent msglist in order to do security checks later
800 if (CC->client_socket > 0) {
801 if (CC->cached_msglist != NULL) {
802 free(CC->cached_msglist);
804 CC->cached_msglist = msglist;
805 CC->cached_num_msgs = num_msgs;
811 return num_processed;
816 * memfmout() - Citadel text formatter and paginator.
817 * Although the original purpose of this routine was to format
818 * text to the reader's screen width, all we're really using it
819 * for here is to format text out to 80 columns before sending it
820 * to the client. The client software may reformat it again.
823 char *mptr, /* where are we going to get our text from? */
824 const char *nl /* string to terminate lines with */
827 unsigned char ch = 0;
834 while (ch=*(mptr++), ch != 0) {
837 if (client_write(outbuf, len) == -1) {
838 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
842 if (client_write(nl, nllen) == -1) {
843 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
848 else if (ch == '\r') {
849 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
851 else if (isspace(ch)) {
852 if (column > 72) { /* Beyond 72 columns, break on the next space */
853 if (client_write(outbuf, len) == -1) {
854 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
858 if (client_write(nl, nllen) == -1) {
859 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
872 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
873 if (client_write(outbuf, len) == -1) {
874 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
878 if (client_write(nl, nllen) == -1) {
879 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
887 if (client_write(outbuf, len) == -1) {
888 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
891 client_write(nl, nllen);
898 * Callback function for mime parser that simply lists the part
900 void list_this_part(char *name, char *filename, char *partnum, char *disp,
901 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
902 char *cbid, void *cbuserdata)
906 ma = (struct ma_info *)cbuserdata;
907 if (ma->is_ma == 0) {
908 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
922 * Callback function for multipart prefix
924 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
925 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
926 char *cbid, void *cbuserdata)
930 ma = (struct ma_info *)cbuserdata;
931 if (!strcasecmp(cbtype, "multipart/alternative")) {
935 if (ma->is_ma == 0) {
936 cprintf("pref=%s|%s\n", partnum, cbtype);
942 * Callback function for multipart sufffix
944 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
945 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
946 char *cbid, void *cbuserdata)
950 ma = (struct ma_info *)cbuserdata;
951 if (ma->is_ma == 0) {
952 cprintf("suff=%s|%s\n", partnum, cbtype);
954 if (!strcasecmp(cbtype, "multipart/alternative")) {
961 * Callback function for mime parser that opens a section for downloading
962 * we use serv_files function here:
964 extern void OpenCmdResult(char *filename, const char *mime_type);
965 void mime_download(char *name, char *filename, char *partnum, char *disp,
966 void *content, char *cbtype, char *cbcharset, size_t length,
967 char *encoding, char *cbid, void *cbuserdata)
971 /* Silently go away if there's already a download open. */
972 if (CC->download_fp != NULL)
976 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
977 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
979 CC->download_fp = tmpfile();
980 if (CC->download_fp == NULL) {
981 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
982 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
986 rv = fwrite(content, length, 1, CC->download_fp);
988 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
989 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
990 fclose(CC->download_fp);
991 CC->download_fp = NULL;
994 fflush(CC->download_fp);
995 rewind(CC->download_fp);
997 OpenCmdResult(filename, cbtype);
1003 * Callback function for mime parser that outputs a section all at once.
1004 * We can specify the desired section by part number *or* content-id.
1006 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1007 void *content, char *cbtype, char *cbcharset, size_t length,
1008 char *encoding, char *cbid, void *cbuserdata)
1010 int *found_it = (int *)cbuserdata;
1013 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1014 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1017 cprintf("%d %d|-1|%s|%s|%s\n",
1024 client_write(content, length);
1029 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
1030 struct CtdlMessage *ret = NULL;
1032 const char *upper_bound;
1034 cit_uint8_t field_header;
1038 upper_bound = Buffer + Length;
1043 // Parse the three bytes that begin EVERY message on disk.
1044 // The first is always 0xFF, the on-disk magic number.
1045 // The second is the anonymous/public type byte.
1046 // The third is the format type byte (vari, fixed, or MIME).
1050 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1053 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1054 memset(ret, 0, sizeof(struct CtdlMessage));
1056 ret->cm_magic = CTDLMESSAGE_MAGIC;
1057 ret->cm_anon_type = *mptr++; // Anon type byte
1058 ret->cm_format_type = *mptr++; // Format type byte
1060 // The rest is zero or more arbitrary fields. Load them in.
1061 // We're done when we encounter either a zero-length field or
1062 // have just processed the 'M' (message text) field.
1065 field_header = '\0';
1068 while (field_header == '\0') { // work around possibly buggy messages
1069 if (mptr >= upper_bound) {
1072 field_header = *mptr++;
1074 if (mptr >= upper_bound) {
1077 which = field_header;
1080 CM_SetField(ret, which, mptr);
1082 mptr += len + 1; // advance to next field
1084 } while ((mptr < upper_bound) && (field_header != 'M'));
1089 // Load a message from disk into memory.
1090 // This is used by CtdlOutputMsg() and other fetch functions.
1092 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1093 // using the CM_Free(); function.
1095 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1096 struct cdbdata dmsgtext;
1097 struct CtdlMessage *ret = NULL;
1099 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1100 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1101 if (dmsgtext.ptr == NULL) {
1102 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1106 if (dmsgtext.ptr[dmsgtext.len - 1] != '\0') {
1107 // FIXME LMDB cannot write to immutable memory
1108 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1109 dmsgtext.ptr[dmsgtext.len - 1] = '\0';
1112 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext.ptr, dmsgtext.len);
1117 // Always make sure there's something in the msg text field. If
1118 // it's NULL, the message text is most likely stored separately,
1119 // so go ahead and fetch that. Failing that, just set a dummy
1120 // body so other code doesn't barf.
1122 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1123 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1124 if (dmsgtext.ptr != NULL) {
1125 CM_SetField(ret, eMesageText, dmsgtext.ptr);
1128 if (CM_IsEmpty(ret, eMesageText)) {
1129 CM_SetField(ret, eMesageText, "\r\n\r\n (no text)\r\n");
1136 // Pre callback function for multipart/alternative
1138 // NOTE: this differs from the standard behavior for a reason. Normally when
1139 // displaying multipart/alternative you want to show the _last_ usable
1140 // format in the message. Here we show the _first_ one, because it's
1141 // usually text/plain. Since this set of functions is designed for text
1142 // output to non-MIME-aware clients, this is the desired behavior.
1144 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1145 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1146 char *cbid, void *cbuserdata)
1150 ma = (struct ma_info *)cbuserdata;
1151 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1152 if (!strcasecmp(cbtype, "multipart/alternative")) {
1156 if (!strcasecmp(cbtype, "message/rfc822")) {
1163 // Post callback function for multipart/alternative
1165 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1166 void *content, char *cbtype, char *cbcharset, size_t length,
1167 char *encoding, char *cbid, void *cbuserdata)
1171 ma = (struct ma_info *)cbuserdata;
1172 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1173 if (!strcasecmp(cbtype, "multipart/alternative")) {
1177 if (!strcasecmp(cbtype, "message/rfc822")) {
1183 // Inline callback function for mime parser that wants to display text
1184 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1185 void *content, char *cbtype, char *cbcharset, size_t length,
1186 char *encoding, char *cbid, void *cbuserdata)
1193 ma = (struct ma_info *)cbuserdata;
1196 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1197 partnum, filename, cbtype, (long)length
1200 // If we're in the middle of a multipart/alternative scope and
1201 // we've already printed another section, skip this one.
1202 if ( (ma->is_ma) && (ma->did_print) ) {
1203 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1208 if ( (!strcasecmp(cbtype, "text/plain"))
1209 || (IsEmptyStr(cbtype)) ) {
1212 client_write(wptr, length);
1213 if (wptr[length-1] != '\n') {
1220 if (!strcasecmp(cbtype, "text/html")) {
1221 ptr = html_to_ascii(content, length, 80, 0);
1223 client_write(ptr, wlen);
1224 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1231 if (ma->use_fo_hooks) {
1232 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1237 if (strncasecmp(cbtype, "multipart/", 10)) {
1238 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1239 partnum, filename, cbtype, (long)length);
1245 // The client is elegant and sophisticated and wants to be choosy about
1246 // MIME content types, so figure out which multipart/alternative part
1247 // we're going to send.
1249 // We use a system of weights. When we find a part that matches one of the
1250 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1251 // and then set ma->chosen_pref to that MIME type's position in our preference
1252 // list. If we then hit another match, we only replace the first match if
1253 // the preference value is lower.
1254 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1255 void *content, char *cbtype, char *cbcharset, size_t length,
1256 char *encoding, char *cbid, void *cbuserdata)
1262 ma = (struct ma_info *)cbuserdata;
1264 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1265 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1266 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1267 if (i < ma->chosen_pref) {
1268 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1269 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1270 ma->chosen_pref = i;
1277 // Now that we've chosen our preferred part, output it.
1278 void output_preferred(char *name,
1292 int add_newline = 0;
1295 char *decoded = NULL;
1296 size_t bytes_decoded;
1299 ma = (struct ma_info *)cbuserdata;
1301 // This is not the MIME part you're looking for...
1302 if (strcasecmp(partnum, ma->chosen_part)) return;
1304 // If the content-type of this part is in our preferred formats
1305 // list, we can simply output it verbatim.
1306 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1307 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1308 if (!strcasecmp(buf, cbtype)) {
1309 /* Yeah! Go! W00t!! */
1310 if (ma->dont_decode == 0)
1311 rc = mime_decode_now (content,
1317 break; // Give us the chance, maybe theres another one.
1319 if (rc == 0) text_content = (char *)content;
1321 text_content = decoded;
1322 length = bytes_decoded;
1325 if (text_content[length-1] != '\n') {
1328 cprintf("Content-type: %s", cbtype);
1329 if (!IsEmptyStr(cbcharset)) {
1330 cprintf("; charset=%s", cbcharset);
1332 cprintf("\nContent-length: %d\n",
1333 (int)(length + add_newline) );
1334 if (!IsEmptyStr(encoding)) {
1335 cprintf("Content-transfer-encoding: %s\n", encoding);
1338 cprintf("Content-transfer-encoding: 7bit\n");
1340 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1342 if (client_write(text_content, length) == -1)
1344 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1347 if (add_newline) cprintf("\n");
1348 if (decoded != NULL) free(decoded);
1353 // No translations required or possible: output as text/plain
1354 cprintf("Content-type: text/plain\n\n");
1356 if (ma->dont_decode == 0)
1357 rc = mime_decode_now (content,
1363 return; // Give us the chance, maybe theres another one.
1365 if (rc == 0) text_content = (char *)content;
1367 text_content = decoded;
1368 length = bytes_decoded;
1371 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1372 if (decoded != NULL) free(decoded);
1377 char desired_section[64];
1383 // Callback function
1384 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1385 void *content, char *cbtype, char *cbcharset, size_t length,
1386 char *encoding, char *cbid, void *cbuserdata)
1388 struct encapmsg *encap;
1390 encap = (struct encapmsg *)cbuserdata;
1392 // Only proceed if this is the desired section...
1393 if (!strcasecmp(encap->desired_section, partnum)) {
1394 encap->msglen = length;
1395 encap->msg = malloc(length + 2);
1396 memcpy(encap->msg, content, length);
1402 // Determine whether the specified message exists in the cached_msglist
1403 // (This is a security check)
1404 int check_cached_msglist(long msgnum) {
1406 // cases in which we skip the check
1407 if (!CC) return om_ok; // not a session
1408 if (CC->client_socket <= 0) return om_ok; // not a client session
1409 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1410 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1412 // Do a binary search within the cached_msglist for the requested msgnum
1414 int max = (CC->cached_num_msgs - 1);
1416 while (max >= min) {
1417 int middle = min + (max-min) / 2 ;
1418 if (msgnum == CC->cached_msglist[middle]) {
1421 if (msgnum > CC->cached_msglist[middle]) {
1429 return om_access_denied;
1433 // Get a message off disk. (returns om_* values found in msgbase.h)
1434 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1435 int mode, // how would you like that message?
1436 int headers_only, // eschew the message body?
1437 int do_proto, // do Citadel protocol responses?
1438 int crlf, // Use CRLF newlines instead of LF?
1439 char *section, // NULL or a message/rfc822 section
1440 int flags, // various flags; see msgbase.h
1445 struct CtdlMessage *TheMessage = NULL;
1446 int retcode = CIT_OK;
1447 struct encapmsg encap;
1450 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1452 (section ? section : "<>")
1455 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1458 if (r == om_not_logged_in) {
1459 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1462 cprintf("%d An unknown error has occurred.\n", ERROR);
1469 * 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,
1493 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1494 * request that we don't even bother loading the body into memory.
1496 if (headers_only == HEADERS_FAST) {
1497 TheMessage = CtdlFetchMessage(msg_num, 0);
1500 TheMessage = CtdlFetchMessage(msg_num, 1);
1503 if (TheMessage == NULL) {
1504 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1505 ERROR + MESSAGE_NOT_FOUND, msg_num);
1506 return(om_no_such_msg);
1509 /* Here is the weird form of this command, to process only an
1510 * encapsulated message/rfc822 section.
1512 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1513 memset(&encap, 0, sizeof encap);
1514 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1515 mime_parser(CM_RANGE(TheMessage, eMesageText),
1516 *extract_encapsulated_message,
1517 NULL, NULL, (void *)&encap, 0
1520 if ((Author != NULL) && (*Author == NULL))
1523 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1525 if ((Address != NULL) && (*Address == NULL))
1528 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1530 if ((MessageID != NULL) && (*MessageID == NULL))
1533 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1535 CM_Free(TheMessage);
1539 encap.msg[encap.msglen] = 0;
1540 TheMessage = convert_internet_message(encap.msg);
1541 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1543 /* Now we let it fall through to the bottom of this
1544 * function, because TheMessage now contains the
1545 * encapsulated message instead of the top-level
1546 * message. Isn't that neat?
1551 cprintf("%d msg %ld has no part %s\n",
1552 ERROR + MESSAGE_NOT_FOUND,
1556 retcode = om_no_such_msg;
1561 /* Ok, output the message now */
1562 if (retcode == CIT_OK)
1563 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1564 if ((Author != NULL) && (*Author == NULL))
1567 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1569 if ((Address != NULL) && (*Address == NULL))
1572 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1574 if ((MessageID != NULL) && (*MessageID == NULL))
1577 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1580 CM_Free(TheMessage);
1586 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1589 char display_name[256];
1591 /* begin header processing loop for Citadel message format */
1592 safestrncpy(display_name, "<unknown>", sizeof display_name);
1593 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1594 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1595 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1596 safestrncpy(display_name, "****", sizeof display_name);
1598 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1599 safestrncpy(display_name, "anonymous", sizeof display_name);
1602 safestrncpy(display_name, buf, sizeof display_name);
1604 if ((is_room_aide())
1605 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1606 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1607 size_t tmp = strlen(display_name);
1608 snprintf(&display_name[tmp],
1609 sizeof display_name - tmp,
1614 /* Now spew the header fields in the order we like them. */
1615 for (i=0; i< NDiskFields; ++i) {
1617 Field = FieldOrder[i];
1618 if (Field != eMesageText) {
1619 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1620 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1621 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1623 if (Field == eAuthor) {
1625 cprintf("%s=%s\n", msgkeys[Field], display_name);
1628 /* Masquerade display name if needed */
1631 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1634 /* Give the client a hint about whether the message originated locally */
1635 if (Field == erFc822Addr) {
1636 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1637 cprintf("locl=yes\n"); // message originated locally.
1649 void OutputRFC822MsgHeaders(
1650 struct CtdlMessage *TheMessage,
1651 int flags, /* should the message be exported clean */
1652 const char *nl, int nlen,
1653 char *mid, long sizeof_mid,
1654 char *suser, long sizeof_suser,
1655 char *luser, long sizeof_luser,
1656 char *fuser, long sizeof_fuser,
1657 char *snode, long sizeof_snode)
1659 char datestamp[100];
1660 int subject_found = 0;
1667 for (i = 0; i < NDiskFields; ++i) {
1668 if (TheMessage->cm_fields[FieldOrder[i]]) {
1669 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1670 switch (FieldOrder[i]) {
1672 safestrncpy(luser, mptr, sizeof_luser);
1673 safestrncpy(suser, mptr, sizeof_suser);
1676 if ((flags & QP_EADDR) != 0) {
1677 mptr = qp_encode_email_addrs(mptr);
1679 sanitize_truncated_recipient(mptr);
1680 cprintf("CC: %s%s", mptr, nl);
1683 cprintf("Return-Path: %s%s", mptr, nl);
1686 cprintf("List-ID: %s%s", mptr, nl);
1689 if ((flags & QP_EADDR) != 0)
1690 mptr = qp_encode_email_addrs(mptr);
1692 while ((*hptr != '\0') && isspace(*hptr))
1694 if (!IsEmptyStr(hptr))
1695 cprintf("Envelope-To: %s%s", hptr, nl);
1698 cprintf("Subject: %s%s", mptr, nl);
1702 safestrncpy(mid, mptr, sizeof_mid);
1705 safestrncpy(fuser, mptr, sizeof_fuser);
1708 if (haschar(mptr, '@') == 0) {
1709 sanitize_truncated_recipient(mptr);
1710 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1714 if ((flags & QP_EADDR) != 0) {
1715 mptr = qp_encode_email_addrs(mptr);
1717 sanitize_truncated_recipient(mptr);
1718 cprintf("To: %s", mptr);
1723 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1724 cprintf("Date: %s%s", datestamp, nl);
1727 cprintf("References: ");
1728 k = num_tokens(mptr, '|');
1729 for (j=0; j<k; ++j) {
1730 extract_token(buf, mptr, j, '|', sizeof buf);
1731 cprintf("<%s>", buf);
1742 while ((*hptr != '\0') && isspace(*hptr))
1744 if (!IsEmptyStr(hptr))
1745 cprintf("Reply-To: %s%s", mptr, nl);
1757 /* these don't map to mime message headers. */
1760 if (mptr != mpptr) {
1765 if (subject_found == 0) {
1766 cprintf("Subject: (no subject)%s", nl);
1771 void Dump_RFC822HeadersBody(
1772 struct CtdlMessage *TheMessage,
1773 int headers_only, /* eschew the message body? */
1774 int flags, /* should the bessage be exported clean? */
1775 const char *nl, int nlen)
1777 cit_uint8_t prev_ch;
1779 const char *StartOfText = StrBufNOTNULL;
1782 int nllen = strlen(nl);
1786 mptr = TheMessage->cm_fields[eMesageText];
1789 while (*mptr != '\0') {
1790 if (*mptr == '\r') {
1797 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1799 eoh = *(mptr+1) == '\n';
1803 StartOfText = strchr(StartOfText, '\n');
1804 StartOfText = strchr(StartOfText, '\n');
1807 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1808 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1809 ((headers_only != HEADERS_NONE) &&
1810 (headers_only != HEADERS_ONLY))
1812 if (*mptr == '\n') {
1813 memcpy(&outbuf[outlen], nl, nllen);
1815 outbuf[outlen] = '\0';
1818 outbuf[outlen++] = *mptr;
1822 if (flags & ESC_DOT) {
1823 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1824 outbuf[outlen++] = '.';
1829 if (outlen > 1000) {
1830 if (client_write(outbuf, outlen) == -1) {
1831 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1834 lfSent = (outbuf[outlen - 1] == '\n');
1839 client_write(outbuf, outlen);
1840 lfSent = (outbuf[outlen - 1] == '\n');
1843 client_write(nl, nlen);
1847 /* If the format type on disk is 1 (fixed-format), then we want
1848 * everything to be output completely literally ... regardless of
1849 * what message transfer format is in use.
1851 void DumpFormatFixed(
1852 struct CtdlMessage *TheMessage,
1853 int mode, /* how would you like that message? */
1854 const char *nl, int nllen)
1862 mptr = TheMessage->cm_fields[eMesageText];
1864 if (mode == MT_MIME) {
1865 cprintf("Content-type: text/plain\n\n");
1869 while (ch = *mptr++, ch > 0) {
1873 if ((buflen > 250) && (!xlline)){
1877 while ((buflen > 0) &&
1878 (!isspace(buf[buflen])))
1884 mptr -= tbuflen - buflen;
1890 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1891 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1896 memcpy (&buf[buflen], nl, nllen);
1900 if (client_write(buf, buflen) == -1) {
1901 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1913 if (!IsEmptyStr(buf)) {
1914 cprintf("%s%s", buf, nl);
1920 * Get a message off disk. (returns om_* values found in msgbase.h)
1922 int CtdlOutputPreLoadedMsg(
1923 struct CtdlMessage *TheMessage,
1924 int mode, /* how would you like that message? */
1925 int headers_only, /* eschew the message body? */
1926 int do_proto, /* do Citadel protocol responses? */
1927 int crlf, /* Use CRLF newlines instead of LF? */
1928 int flags /* should the bessage be exported clean? */
1931 const char *nl; /* newline string */
1935 /* Buffers needed for RFC822 translation. These are all filled
1936 * using functions that are bounds-checked, and therefore we can
1937 * make them substantially smaller than SIZ.
1945 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1946 ((TheMessage == NULL) ? "NULL" : "not null"),
1947 mode, headers_only, do_proto, crlf
1950 strcpy(mid, "unknown");
1951 nl = (crlf ? "\r\n" : "\n");
1952 nlen = crlf ? 2 : 1;
1954 if (!CM_IsValidMsg(TheMessage)) {
1955 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1956 return(om_no_such_msg);
1959 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1960 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1962 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1963 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1966 /* Are we downloading a MIME component? */
1967 if (mode == MT_DOWNLOAD) {
1968 if (TheMessage->cm_format_type != FMT_RFC822) {
1970 cprintf("%d This is not a MIME message.\n",
1971 ERROR + ILLEGAL_VALUE);
1972 } else if (CC->download_fp != NULL) {
1973 if (do_proto) cprintf(
1974 "%d You already have a download open.\n",
1975 ERROR + RESOURCE_BUSY);
1977 /* Parse the message text component */
1978 mime_parser(CM_RANGE(TheMessage, eMesageText),
1979 *mime_download, NULL, NULL, NULL, 0);
1980 /* If there's no file open by this time, the requested
1981 * section wasn't found, so print an error
1983 if (CC->download_fp == NULL) {
1984 if (do_proto) cprintf(
1985 "%d Section %s not found.\n",
1986 ERROR + FILE_NOT_FOUND,
1987 CC->download_desired_section);
1990 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1993 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1994 // in a single server operation instead of opening a download file.
1995 if (mode == MT_SPEW_SECTION) {
1996 if (TheMessage->cm_format_type != FMT_RFC822) {
1998 cprintf("%d This is not a MIME message.\n",
1999 ERROR + ILLEGAL_VALUE);
2002 // Locate and parse the component specified by the caller
2004 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2006 // If section wasn't found, print an error
2009 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
2013 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2016 // now for the user-mode message reading loops
2017 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2019 // Does the caller want to skip the headers?
2020 if (headers_only == HEADERS_NONE) goto START_TEXT;
2022 // Tell the client which format type we're using.
2023 if ( (mode == MT_CITADEL) && (do_proto) ) {
2024 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2027 // nhdr=yes means that we're only displaying headers, no body
2028 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2029 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2032 cprintf("nhdr=yes\n");
2035 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2036 OutputCtdlMsgHeaders(TheMessage, do_proto);
2039 // begin header processing loop for RFC822 transfer format
2044 if (mode == MT_RFC822)
2045 OutputRFC822MsgHeaders(
2050 suser, sizeof(suser),
2051 luser, sizeof(luser),
2052 fuser, sizeof(fuser),
2053 snode, sizeof(snode)
2057 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2058 suser[i] = tolower(suser[i]);
2059 if (!isalnum(suser[i])) suser[i]='_';
2062 if (mode == MT_RFC822) {
2063 /* Construct a fun message id */
2064 cprintf("Message-ID: <%s", mid);
2065 if (strchr(mid, '@')==NULL) {
2066 cprintf("@%s", snode);
2070 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2071 cprintf("From: \"----\" <x@x.org>%s", nl);
2073 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2074 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2076 else if (!IsEmptyStr(fuser)) {
2077 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2080 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2083 /* Blank line signifying RFC822 end-of-headers */
2084 if (TheMessage->cm_format_type != FMT_RFC822) {
2089 // end header processing loop ... at this point, we're in the text
2091 if (headers_only == HEADERS_FAST) goto DONE;
2093 // Tell the client about the MIME parts in this message
2094 if (TheMessage->cm_format_type == FMT_RFC822) {
2095 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2096 memset(&ma, 0, sizeof(struct ma_info));
2097 mime_parser(CM_RANGE(TheMessage, eMesageText),
2098 (do_proto ? *list_this_part : NULL),
2099 (do_proto ? *list_this_pref : NULL),
2100 (do_proto ? *list_this_suff : NULL),
2103 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2104 Dump_RFC822HeadersBody(
2113 if (headers_only == HEADERS_ONLY) {
2117 // signify start of msg text
2118 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2119 if (do_proto) cprintf("text\n");
2122 if (TheMessage->cm_format_type == FMT_FIXED)
2125 mode, // how would you like that message?
2128 // If the message on disk is format 0 (Citadel vari-format), we
2129 // output using the formatter at 80 columns. This is the final output
2130 // form if the transfer format is RFC822, but if the transfer format
2131 // is Citadel proprietary, it'll still work, because the indentation
2132 // for new paragraphs is correct and the client will reformat the
2133 // message to the reader's screen width.
2135 if (TheMessage->cm_format_type == FMT_CITADEL) {
2136 if (mode == MT_MIME) {
2137 cprintf("Content-type: text/x-citadel-variformat\n\n");
2139 memfmout(TheMessage->cm_fields[eMesageText], nl);
2142 // If the message on disk is format 4 (MIME), we've gotta hand it
2143 // off to the MIME parser. The client has already been told that
2144 // this message is format 1 (fixed format), so the callback function
2145 // we use will display those parts as-is.
2147 if (TheMessage->cm_format_type == FMT_RFC822) {
2148 memset(&ma, 0, sizeof(struct ma_info));
2150 if (mode == MT_MIME) {
2151 ma.use_fo_hooks = 0;
2152 strcpy(ma.chosen_part, "1");
2153 ma.chosen_pref = 9999;
2154 ma.dont_decode = CC->msg4_dont_decode;
2155 mime_parser(CM_RANGE(TheMessage, eMesageText),
2156 *choose_preferred, *fixed_output_pre,
2157 *fixed_output_post, (void *)&ma, 1);
2158 mime_parser(CM_RANGE(TheMessage, eMesageText),
2159 *output_preferred, NULL, NULL, (void *)&ma, 1);
2162 ma.use_fo_hooks = 1;
2163 mime_parser(CM_RANGE(TheMessage, eMesageText),
2164 *fixed_output, *fixed_output_pre,
2165 *fixed_output_post, (void *)&ma, 0);
2170 DONE: /* now we're done */
2171 if (do_proto) cprintf("000\n");
2175 // Save one or more message pointers into a specified room
2176 // (Returns 0 for success, nonzero for failure)
2177 // roomname may be NULL to use the current room
2179 // Note that the 'supplied_msg' field may be set to NULL, in which case
2180 // the message will be fetched from disk, by number, if we need to perform
2181 // replication checks. This adds an additional database read, so if the
2182 // caller already has the message in memory then it should be supplied. (Obviously
2183 // this mode of operation only works if we're saving a single message.)
2185 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2186 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2189 char hold_rm[ROOMNAMELEN];
2190 struct cdbdata *cdbfr;
2193 long highest_msg = 0L;
2196 struct CtdlMessage *msg = NULL;
2198 long *msgs_to_be_merged = NULL;
2199 int num_msgs_to_be_merged = 0;
2202 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2203 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2206 strcpy(hold_rm, CC->room.QRname);
2209 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2210 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2211 if (num_newmsgs > 1) supplied_msg = NULL;
2213 /* Now the regular stuff */
2214 if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
2215 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2216 return(ERROR + ROOM_NOT_FOUND);
2219 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2220 num_msgs_to_be_merged = 0;
2221 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2223 /* Create a list of msgid's which were supplied by the caller, but do
2224 * not already exist in the target room. It is absolutely taboo to
2225 * have more than one reference to the same message in a room.
2227 for (i=0; i<num_newmsgs; ++i) {
2229 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2230 if (msglist[j] == newmsgidlist[i]) {
2235 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2239 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2242 * Now merge the new messages
2244 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2245 if (msglist == NULL) {
2246 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2247 free(msgs_to_be_merged);
2248 abort(); // FIXME FIXME FOOFOO
2249 return (ERROR + INTERNAL_ERROR);
2251 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2252 num_msgs += num_msgs_to_be_merged;
2254 /* Sort the message list, so all the msgid's are in order */
2255 num_msgs = sort_msglist(msglist, num_msgs);
2257 /* Determine the highest message number */
2258 highest_msg = msglist[num_msgs - 1];
2260 /* Write it back to disk. */
2261 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2263 /* Free up the memory we used. */
2266 /* Update the highest-message pointer and unlock the room. */
2267 CC->room.QRhighest = highest_msg;
2268 CtdlPutRoomLock(&CC->room);
2270 /* Perform replication checks if necessary */
2271 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2272 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2274 for (i=0; i<num_msgs_to_be_merged; ++i) {
2275 msgid = msgs_to_be_merged[i];
2277 if (supplied_msg != NULL) {
2281 msg = CtdlFetchMessage(msgid, 0);
2285 ReplicationChecks(msg);
2287 /* If the message has an Exclusive ID, index that... */
2288 if (!CM_IsEmpty(msg, eExclusiveID)) {
2289 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2292 /* Free up the memory we may have allocated */
2293 if (msg != supplied_msg) {
2302 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2305 /* Submit this room for processing by hooks */
2306 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2307 if (total_roomhook_errors) {
2308 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2311 /* Go back to the room we were in before we wandered here... */
2312 CtdlGetRoom(&CC->room, hold_rm);
2314 /* Bump the reference count for all messages which were merged */
2315 if (!suppress_refcount_adj) {
2316 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2319 /* Free up memory... */
2320 if (msgs_to_be_merged != NULL) {
2321 free(msgs_to_be_merged);
2324 /* Return success. */
2330 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2333 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
2334 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2339 * Message base operation to save a new message to the message store
2340 * (returns new message number)
2342 * This is the back end for CtdlSubmitMsg() and should not be directly
2343 * called by server-side modules.
2346 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2354 * If the message is big, set its body aside for storage elsewhere
2355 * and we hide the message body from the serializer
2357 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2359 holdM = msg->cm_fields[eMesageText];
2360 msg->cm_fields[eMesageText] = NULL;
2361 holdMLen = msg->cm_lengths[eMesageText];
2362 msg->cm_lengths[eMesageText] = 0;
2365 /* Serialize our data structure for storage in the database */
2366 CtdlSerializeMessage(&smr, msg);
2369 /* put the message body back into the message */
2370 msg->cm_fields[eMesageText] = holdM;
2371 msg->cm_lengths[eMesageText] = holdMLen;
2376 cprintf("%d Unable to serialize message\n",
2377 ERROR + INTERNAL_ERROR);
2380 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2386 /* Write our little bundle of joy into the message base */
2387 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2389 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2393 retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
2395 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2400 /* Free the memory we used for the serialized message */
2406 long send_message(struct CtdlMessage *msg) {
2412 /* Get a new message number */
2413 newmsgid = get_new_message_number();
2415 /* Generate an ID if we don't have one already */
2416 if (CM_IsEmpty(msg, emessageId)) {
2417 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2418 (long unsigned int) time(NULL),
2419 (long unsigned int) newmsgid,
2420 CtdlGetConfigStr("c_fqdn")
2423 CM_SetField(msg, emessageId, msgidbuf);
2426 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2432 /* Return the *local* message ID to the caller
2433 * (even if we're storing an incoming network message)
2440 * Serialize a struct CtdlMessage into the format used on disk.
2442 * This function loads up a "struct ser_ret" (defined in server.h) which
2443 * contains the length of the serialized message and a pointer to the
2444 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2446 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2447 struct CtdlMessage *msg) /* unserialized msg */
2453 * Check for valid message format
2455 if (CM_IsValidMsg(msg) == 0) {
2456 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2463 for (i=0; i < NDiskFields; ++i)
2464 if (msg->cm_fields[FieldOrder[i]] != NULL)
2465 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2467 ret->ser = malloc(ret->len);
2468 if (ret->ser == NULL) {
2469 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2476 ret->ser[1] = msg->cm_anon_type;
2477 ret->ser[2] = msg->cm_format_type;
2480 for (i=0; i < NDiskFields; ++i) {
2481 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2482 ret->ser[wlen++] = (char)FieldOrder[i];
2484 memcpy(&ret->ser[wlen],
2485 msg->cm_fields[FieldOrder[i]],
2486 msg->cm_lengths[FieldOrder[i]] + 1);
2488 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2492 if (ret->len != wlen) {
2493 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2501 * Check to see if any messages already exist in the current room which
2502 * carry the same Exclusive ID as this one. If any are found, delete them.
2504 void ReplicationChecks(struct CtdlMessage *msg) {
2505 long old_msgnum = (-1L);
2507 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2509 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2511 /* No exclusive id? Don't do anything. */
2512 if (msg == NULL) return;
2513 if (CM_IsEmpty(msg, eExclusiveID)) return;
2515 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2516 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2518 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2519 if (old_msgnum > 0L) {
2520 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2521 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2527 * Save a message to disk and submit it into the delivery system.
2529 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2530 struct recptypes *recps, /* recipients (if mail) */
2531 const char *force /* force a particular room? */
2533 char hold_rm[ROOMNAMELEN];
2534 char actual_rm[ROOMNAMELEN];
2535 char force_room[ROOMNAMELEN];
2536 char content_type[SIZ]; /* We have to learn this */
2537 char recipient[SIZ];
2538 char bounce_to[1024];
2541 const char *mptr = NULL;
2542 struct ctdluser userbuf;
2544 struct MetaData smi;
2545 char *collected_addresses = NULL;
2546 struct addresses_to_be_filed *aptr = NULL;
2547 StrBuf *saved_rfc822_version = NULL;
2548 int qualified_for_journaling = 0;
2550 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2551 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2553 /* If this message has no timestamp, we take the liberty of
2554 * giving it one, right now.
2556 if (CM_IsEmpty(msg, eTimestamp)) {
2557 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2560 /* If this message has no path, we generate one.
2562 if (CM_IsEmpty(msg, eMessagePath)) {
2563 if (!CM_IsEmpty(msg, eAuthor)) {
2564 CM_CopyField(msg, eMessagePath, eAuthor);
2565 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2566 if (isspace(msg->cm_fields[eMessagePath][a])) {
2567 msg->cm_fields[eMessagePath][a] = ' ';
2572 CM_SetField(msg, eMessagePath, "unknown");
2576 if (force == NULL) {
2577 force_room[0] = '\0';
2580 strcpy(force_room, force);
2583 /* Learn about what's inside, because it's what's inside that counts */
2584 if (CM_IsEmpty(msg, eMesageText)) {
2585 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2589 switch (msg->cm_format_type) {
2591 strcpy(content_type, "text/x-citadel-variformat");
2594 strcpy(content_type, "text/plain");
2597 strcpy(content_type, "text/plain");
2598 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2601 safestrncpy(content_type, &mptr[13], sizeof content_type);
2602 string_trim(content_type);
2603 aptr = content_type;
2604 while (!IsEmptyStr(aptr)) {
2616 /* Goto the correct room */
2617 room = (recps) ? CC->room.QRname : SENTITEMS;
2618 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2619 strcpy(hold_rm, CC->room.QRname);
2620 strcpy(actual_rm, CC->room.QRname);
2621 if (recps != NULL) {
2622 strcpy(actual_rm, SENTITEMS);
2625 /* If the user is a twit, move to the twit room for posting */
2627 if (CC->user.axlevel == AxProbU) {
2628 strcpy(hold_rm, actual_rm);
2629 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2630 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2634 /* ...or if this message is destined for Aide> then go there. */
2635 if (!IsEmptyStr(force_room)) {
2636 strcpy(actual_rm, force_room);
2639 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2640 if (strcasecmp(actual_rm, CC->room.QRname)) {
2641 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2645 * If this message has no O (room) field, generate one.
2647 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2648 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2651 /* Perform "before save" hooks (aborting if any return nonzero) */
2652 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2653 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2656 * If this message has an Exclusive ID, and the room is replication
2657 * checking enabled, then do replication checks.
2659 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2660 ReplicationChecks(msg);
2663 /* Save it to disk */
2664 syslog(LOG_DEBUG, "msgbase: saving to disk");
2665 newmsgid = send_message(msg);
2666 if (newmsgid <= 0L) return(-5);
2668 /* Write a supplemental message info record. This doesn't have to
2669 * be a critical section because nobody else knows about this message
2672 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2673 memset(&smi, 0, sizeof(struct MetaData));
2674 smi.meta_msgnum = newmsgid;
2675 smi.meta_refcount = 0;
2676 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2679 * Measure how big this message will be when rendered as RFC822.
2680 * We do this for two reasons:
2681 * 1. We need the RFC822 length for the new metadata record, so the
2682 * POP and IMAP services don't have to calculate message lengths
2683 * while the user is waiting (multiplied by potentially hundreds
2684 * or thousands of messages).
2685 * 2. If journaling is enabled, we will need an RFC822 version of the
2686 * message to attach to the journalized copy.
2688 if (CC->redirect_buffer != NULL) {
2689 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2690 exit(CTDLEXIT_REDIRECT);
2692 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2693 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2694 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2695 saved_rfc822_version = CC->redirect_buffer;
2696 CC->redirect_buffer = NULL;
2700 /* Now figure out where to store the pointers */
2701 syslog(LOG_DEBUG, "msgbase: storing pointers");
2703 /* If this is being done by the networker delivering a private
2704 * message, we want to BYPASS saving the sender's copy (because there
2705 * is no local sender; it would otherwise go to the Trashcan).
2707 if ((!CC->internal_pgm) || (recps == NULL)) {
2708 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2709 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2710 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2714 /* For internet mail, drop a copy in the outbound queue room */
2715 if ((recps != NULL) && (recps->num_internet > 0)) {
2716 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2719 /* If other rooms are specified, drop them there too. */
2720 if ((recps != NULL) && (recps->num_room > 0)) {
2721 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2722 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2723 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2724 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2728 /* Decide where bounces need to be delivered */
2729 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2730 if (CC->logged_in) {
2731 strcpy(bounce_to, CC->user.fullname);
2733 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2734 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2736 recps->bounce_to = bounce_to;
2739 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2741 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2742 if ((recps != NULL) && (recps->num_local > 0)) {
2746 pch = recps->recp_local;
2747 recps->recp_local = recipient;
2748 ntokens = num_tokens(pch, '|');
2749 for (i=0; i<ntokens; ++i) {
2750 extract_token(recipient, pch, i, '|', sizeof recipient);
2751 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2752 if (CtdlGetUser(&userbuf, recipient) == 0) {
2753 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2754 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2755 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2756 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2759 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2760 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2763 recps->recp_local = pch;
2766 /* Perform "after save" hooks */
2767 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2769 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2770 CM_FlushField(msg, eVltMsgNum);
2772 /* Go back to the room we started from */
2773 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2774 if (strcasecmp(hold_rm, CC->room.QRname)) {
2775 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2779 * Any addresses to harvest for someone's address book?
2781 if ( (CC->logged_in) && (recps != NULL) ) {
2782 collected_addresses = harvest_collected_addresses(msg);
2785 if (collected_addresses != NULL) {
2786 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2787 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2788 aptr->roomname = strdup(actual_rm);
2789 aptr->collected_addresses = collected_addresses;
2790 begin_critical_section(S_ATBF);
2793 end_critical_section(S_ATBF);
2797 * Determine whether this message qualifies for journaling.
2799 if (!CM_IsEmpty(msg, eJournal)) {
2800 qualified_for_journaling = 0;
2803 if (recps == NULL) {
2804 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2806 else if (recps->num_local + recps->num_internet > 0) {
2807 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2810 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2815 * Do we have to perform journaling? If so, hand off the saved
2816 * RFC822 version will be handed off to the journaler for background
2817 * submit. Otherwise, we have to free the memory ourselves.
2819 if (saved_rfc822_version != NULL) {
2820 if (qualified_for_journaling) {
2821 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2824 FreeStrBuf(&saved_rfc822_version);
2828 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2829 recps->bounce_to = NULL;
2837 * Convenience function for generating small administrative messages.
2839 long quickie_message(char *from,
2847 struct CtdlMessage *msg;
2848 struct recptypes *recp = NULL;
2850 msg = malloc(sizeof(struct CtdlMessage));
2851 memset(msg, 0, sizeof(struct CtdlMessage));
2852 msg->cm_magic = CTDLMESSAGE_MAGIC;
2853 msg->cm_anon_type = MES_NORMAL;
2854 msg->cm_format_type = format_type;
2856 if (!IsEmptyStr(from)) {
2857 CM_SetField(msg, eAuthor, from);
2859 else if (!IsEmptyStr(fromaddr)) {
2861 CM_SetField(msg, eAuthor, fromaddr);
2862 pAt = strchr(msg->cm_fields[eAuthor], '@');
2864 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2868 msg->cm_fields[eAuthor] = strdup("Citadel");
2871 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr);
2872 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
2873 if (!IsEmptyStr(to)) {
2874 CM_SetField(msg, eRecipient, to);
2875 recp = validate_recipients(to, NULL, 0);
2877 if (!IsEmptyStr(subject)) {
2878 CM_SetField(msg, eMsgSubject, subject);
2880 if (!IsEmptyStr(text)) {
2881 CM_SetField(msg, eMesageText, text);
2884 long msgnum = CtdlSubmitMsg(msg, recp, room);
2886 if (recp != NULL) free_recipients(recp);
2892 * Back end function used by CtdlMakeMessage() and similar functions
2894 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2896 size_t maxlen, // maximum message length
2897 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2898 int crlf // CRLF newlines instead of LF
2906 LineBuf = NewStrBufPlain(NULL, SIZ);
2907 if (exist == NULL) {
2908 Message = NewStrBufPlain(NULL, 4 * SIZ);
2911 Message = NewStrBufDup(exist);
2914 /* Do we need to change leading ".." to "." for SMTP escaping? */
2915 if ((tlen == 1) && (*terminator == '.')) {
2919 /* read in the lines of message text one by one */
2921 if (CtdlClientGetLine(LineBuf) < 0) {
2924 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2927 if ( (!flushing) && (!finished) ) {
2929 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2932 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2935 /* Unescape SMTP-style input of two dots at the beginning of the line */
2936 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2937 StrBufCutLeft(LineBuf, 1);
2939 StrBufAppendBuf(Message, LineBuf, 0);
2942 /* if we've hit the max msg length, flush the rest */
2943 if (StrLength(Message) >= maxlen) {
2947 } while (!finished);
2948 FreeStrBuf(&LineBuf);
2951 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2958 // Back end function used by CtdlMakeMessage() and similar functions
2959 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2961 size_t maxlen, // maximum message length
2962 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2963 int crlf // CRLF newlines instead of LF
2967 Message = CtdlReadMessageBodyBuf(terminator,
2973 if (Message == NULL) {
2977 return SmashStrBuf(&Message);
2982 struct CtdlMessage *CtdlMakeMessage(
2983 struct ctdluser *author, /* author's user structure */
2984 char *recipient, /* NULL if it's not mail */
2985 char *recp_cc, /* NULL if it's not mail */
2986 char *room, /* room where it's going */
2987 int type, /* see MES_ types in header file */
2988 int format_type, /* variformat, plain text, MIME... */
2989 char *fake_name, /* who we're masquerading as */
2990 char *my_email, /* which of my email addresses to use (empty is ok) */
2991 char *subject, /* Subject (optional) */
2992 char *supplied_euid, /* ...or NULL if this is irrelevant */
2993 char *preformatted_text, /* ...or NULL to read text from client */
2994 char *references /* Thread references */
2996 return CtdlMakeMessageLen(
2997 author, /* author's user structure */
2998 recipient, /* NULL if it's not mail */
2999 (recipient)?strlen(recipient) : 0,
3000 recp_cc, /* NULL if it's not mail */
3001 (recp_cc)?strlen(recp_cc): 0,
3002 room, /* room where it's going */
3003 (room)?strlen(room): 0,
3004 type, /* see MES_ types in header file */
3005 format_type, /* variformat, plain text, MIME... */
3006 fake_name, /* who we're masquerading as */
3007 (fake_name)?strlen(fake_name): 0,
3008 my_email, /* which of my email addresses to use (empty is ok) */
3009 (my_email)?strlen(my_email): 0,
3010 subject, /* Subject (optional) */
3011 (subject)?strlen(subject): 0,
3012 supplied_euid, /* ...or NULL if this is irrelevant */
3013 (supplied_euid)?strlen(supplied_euid):0,
3014 preformatted_text, /* ...or NULL to read text from client */
3015 (preformatted_text)?strlen(preformatted_text) : 0,
3016 references, /* Thread references */
3017 (references)?strlen(references):0);
3023 * Build a binary message to be saved on disk.
3024 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3025 * will become part of the message. This means you are no longer
3026 * responsible for managing that memory -- it will be freed along with
3027 * the rest of the fields when CM_Free() is called.)
3029 struct CtdlMessage *CtdlMakeMessageLen(
3030 struct ctdluser *author, /* author's user structure */
3031 char *recipient, /* NULL if it's not mail */
3033 char *recp_cc, /* NULL if it's not mail */
3035 char *room, /* room where it's going */
3037 int type, /* see MES_ types in header file */
3038 int format_type, /* variformat, plain text, MIME... */
3039 char *fake_name, /* who we're masquerading as */
3041 char *my_email, /* which of my email addresses to use (empty is ok) */
3043 char *subject, /* Subject (optional) */
3045 char *supplied_euid, /* ...or NULL if this is irrelevant */
3047 char *preformatted_text, /* ...or NULL to read text from client */
3049 char *references, /* Thread references */
3054 struct CtdlMessage *msg;
3056 StrBuf *FakeEncAuthor = NULL;
3058 msg = malloc(sizeof(struct CtdlMessage));
3059 memset(msg, 0, sizeof(struct CtdlMessage));
3060 msg->cm_magic = CTDLMESSAGE_MAGIC;
3061 msg->cm_anon_type = type;
3062 msg->cm_format_type = format_type;
3064 if (recipient != NULL) rcplen = string_trim(recipient);
3065 if (recp_cc != NULL) cclen = string_trim(recp_cc);
3067 /* Path or Return-Path */
3069 CM_SetField(msg, eMessagePath, my_email);
3071 else if (!IsEmptyStr(author->fullname)) {
3072 CM_SetField(msg, eMessagePath, author->fullname);
3074 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3076 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3077 CM_SetField(msg, eTimestamp, buf);
3080 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3083 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3085 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3086 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3087 FreeStrBuf(&FakeAuthor);
3089 if (!!IsEmptyStr(CC->room.QRname)) {
3090 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3091 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11]);
3094 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
3099 CM_SetField(msg, eRecipient, recipient);
3102 CM_SetField(msg, eCarbonCopY, recp_cc);
3106 CM_SetField(msg, erFc822Addr, my_email);
3108 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3109 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
3112 if (subject != NULL) {
3114 length = string_trim(subject);
3120 while ((subject[i] != '\0') &&
3121 (IsAscii = isascii(subject[i]) != 0 ))
3124 CM_SetField(msg, eMsgSubject, subject);
3125 else /* ok, we've got utf8 in the string. */
3128 rfc2047Subj = rfc2047encode(subject, length);
3129 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3136 CM_SetField(msg, eExclusiveID, supplied_euid);
3140 CM_SetField(msg, eWeferences, references);
3143 if (preformatted_text != NULL) {
3144 CM_SetField(msg, eMesageText, preformatted_text);
3148 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3149 if (MsgBody != NULL) {
3150 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3159 * API function to delete messages which match a set of criteria
3160 * (returns the actual number of messages deleted)
3162 int CtdlDeleteMessages(const char *room_name, // which room
3163 long *dmsgnums, // array of msg numbers to be deleted
3164 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3165 char *content_type // or "" for any. regular expressions expected.
3167 struct ctdlroom qrbuf;
3168 struct cdbdata *cdbfr;
3169 long *msglist = NULL;
3170 long *dellist = NULL;
3173 int num_deleted = 0;
3175 struct MetaData smi;
3178 int need_to_free_re = 0;
3180 if (content_type) if (!IsEmptyStr(content_type)) {
3181 regcomp(&re, content_type, 0);
3182 need_to_free_re = 1;
3184 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3186 /* get room record, obtaining a lock... */
3187 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3188 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3189 if (need_to_free_re) regfree(&re);
3190 return(0); /* room not found */
3193 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3195 dellist = malloc(num_msgs * sizeof(long));
3196 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3197 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3198 int have_more_del = 1;
3200 num_msgs = sort_msglist(msglist, num_msgs);
3201 if (num_dmsgnums > 1) {
3202 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3206 while ((i < num_msgs) && (have_more_del)) {
3209 /* Set/clear a bit for each criterion */
3211 /* 0 messages in the list or a null list means that we are
3212 * interested in deleting any messages which meet the other criteria.
3215 delete_this |= 0x01;
3218 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3223 if (msglist[i] == dmsgnums[j]) {
3224 delete_this |= 0x01;
3227 have_more_del = (j < num_dmsgnums);
3230 if (have_contenttype) {
3231 GetMetaData(&smi, msglist[i]);
3232 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3233 delete_this |= 0x02;
3236 delete_this |= 0x02;
3239 /* Delete message only if all bits are set */
3240 if (delete_this == 0x03) {
3241 dellist[num_deleted++] = msglist[i];
3247 num_msgs = sort_msglist(msglist, num_msgs);
3248 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3251 qrbuf.QRhighest = msglist[num_msgs - 1];
3254 qrbuf.QRhighest = 0;
3257 CtdlPutRoomLock(&qrbuf);
3259 /* Go through the messages we pulled out of the index, and decrement
3260 * their reference counts by 1. If this is the only room the message
3261 * was in, the reference count will reach zero and the message will
3262 * automatically be deleted from the database. We do this in a
3263 * separate pass because there might be plug-in hooks getting called,
3264 * and we don't want that happening during an S_ROOMS critical
3268 for (i=0; i<num_deleted; ++i) {
3269 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3271 AdjRefCountList(dellist, num_deleted, -1);
3273 /* Now free the memory we used, and go away. */
3274 if (msglist != NULL) free(msglist);
3275 if (dellist != NULL) free(dellist);
3276 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3277 if (need_to_free_re) regfree(&re);
3278 return (num_deleted);
3283 * GetMetaData() - Get the supplementary record for a message
3285 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3286 struct cdbdata cdbsmi;
3289 memset(smibuf, 0, sizeof(struct MetaData));
3290 smibuf->meta_msgnum = msgnum;
3291 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3293 /* Use the negative of the message number for its supp record index */
3294 TheIndex = (0L - msgnum);
3296 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3297 if (cdbsmi.ptr == NULL) {
3298 return; /* record not found; leave it alone */
3300 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3306 * PutMetaData() - (re)write supplementary record for a message
3308 void PutMetaData(struct MetaData *smibuf)
3312 /* Use the negative of the message number for the metadata db index */
3313 TheIndex = (0L - smibuf->meta_msgnum);
3315 cdb_store(CDB_MSGMAIN,
3316 &TheIndex, (int)sizeof(long),
3317 smibuf, (int)sizeof(struct MetaData)
3322 // Convenience function to process a big block of AdjRefCount() operations
3323 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3326 for (i = 0; i < nmsg; i++) {
3327 AdjRefCount(msgnum[i], incr);
3332 // AdjRefCount - adjust the reference count for a message.
3333 // We need to delete from disk any message whose reference count reaches zero.
3334 void AdjRefCount(long msgnum, int incr) {
3335 struct MetaData smi;
3338 // This is a *tight* critical section; please keep it that way, as
3339 // it may get called while nested in other critical sections.
3340 // Complicating this any further will surely cause deadlock!
3341 begin_critical_section(S_SUPPMSGMAIN);
3342 GetMetaData(&smi, msgnum);
3343 smi.meta_refcount += incr;
3345 end_critical_section(S_SUPPMSGMAIN);
3346 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3348 // If the reference count is now zero, delete both the message and its metadata record.
3349 if (smi.meta_refcount == 0) {
3350 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3352 // Call delete hooks with NULL room to show it has gone altogether
3353 PerformDeleteHooks(NULL, msgnum);
3355 // Remove from message base
3357 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3358 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3360 // Remove metadata record
3361 delnum = (0L - msgnum);
3362 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3367 // Write a generic object to this room
3368 // Returns the message number of the written object, in case you need it.
3369 long CtdlWriteObject(char *req_room, // Room to stuff it in
3370 char *content_type, // MIME type of this object
3371 char *raw_message, // Data to be written
3372 off_t raw_length, // Size of raw_message
3373 struct ctdluser *is_mailbox, // Mailbox room?
3374 int is_binary, // Is encoding necessary?
3375 unsigned int flags // Internal save flags
3377 struct ctdlroom qrbuf;
3378 char roomname[ROOMNAMELEN];
3379 struct CtdlMessage *msg;
3380 StrBuf *encoded_message = NULL;
3382 if (is_mailbox != NULL) {
3383 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3386 safestrncpy(roomname, req_room, sizeof(roomname));
3389 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3392 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3395 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3398 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3399 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3400 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3403 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3406 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3410 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3413 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3416 syslog(LOG_DEBUG, "msgbase: allocating");
3417 msg = malloc(sizeof(struct CtdlMessage));
3418 memset(msg, 0, sizeof(struct CtdlMessage));
3419 msg->cm_magic = CTDLMESSAGE_MAGIC;
3420 msg->cm_anon_type = MES_NORMAL;
3421 msg->cm_format_type = 4;
3422 CM_SetField(msg, eAuthor, CC->user.fullname);
3423 CM_SetField(msg, eOriginalRoom, req_room);
3424 msg->cm_flags = flags;
3426 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3428 /* Create the requested room if we have to. */
3429 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3430 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3433 /* Now write the data */
3434 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3440 /************************************************************************/
3441 /* MODULE INITIALIZATION */
3442 /************************************************************************/
3444 char *ctdl_module_init_msgbase(void) {
3446 FillMsgKeyLookupTable();
3449 /* return our module id for the log */