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) 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 CtdlModuleDoSearch(search_string, "fulltext");
723 //FIXME FOOFOO DO ARRAY
725 if (num_search_msgs > 0) {
729 orig_num_msgs = num_msgs;
731 for (i=0; i<orig_num_msgs; ++i) {
732 for (j=0; j<num_search_msgs; ++j) {
733 if (msglist[i] == search_msgs[j]) {
734 msglist[num_msgs++] = msglist[i];
740 num_msgs = 0; /* No messages qualify */
742 if (search_msgs != NULL) free(search_msgs);
744 /* Now that we've purged messages which don't contain the search
745 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
752 * Now iterate through the message list, according to the
753 * criteria supplied by the caller.
756 for (a = 0; a < num_msgs; ++a) {
757 if (server_shutting_down) {
758 if (need_to_free_re) regfree(&re);
760 return num_processed;
762 thismsg = msglist[a];
763 if (mode == MSGS_ALL) {
767 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
768 if (is_seen) lastold = thismsg;
774 || ((mode == MSGS_OLD) && (is_seen))
775 || ((mode == MSGS_NEW) && (!is_seen))
776 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
777 || ((mode == MSGS_FIRST) && (a < ref))
778 || ((mode == MSGS_GT) && (thismsg > ref))
779 || ((mode == MSGS_LT) && (thismsg < ref))
780 || ((mode == MSGS_EQ) && (thismsg == ref))
783 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
785 CallBack(lastold, userdata);
791 CallBack(thismsg, userdata);
796 if (need_to_free_re) regfree(&re);
799 * We cache the most recent msglist in order to do security checks later
801 if (CC->client_socket > 0) {
802 if (CC->cached_msglist != NULL) {
803 free(CC->cached_msglist);
805 CC->cached_msglist = msglist;
806 CC->cached_num_msgs = num_msgs;
812 return num_processed;
817 * memfmout() - Citadel text formatter and paginator.
818 * Although the original purpose of this routine was to format
819 * text to the reader's screen width, all we're really using it
820 * for here is to format text out to 80 columns before sending it
821 * to the client. The client software may reformat it again.
824 char *mptr, /* where are we going to get our text from? */
825 const char *nl /* string to terminate lines with */
828 unsigned char ch = 0;
835 while (ch=*(mptr++), ch != 0) {
838 if (client_write(outbuf, len) == -1) {
839 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
843 if (client_write(nl, nllen) == -1) {
844 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
849 else if (ch == '\r') {
850 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
852 else if (isspace(ch)) {
853 if (column > 72) { /* Beyond 72 columns, break on the next space */
854 if (client_write(outbuf, len) == -1) {
855 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
859 if (client_write(nl, nllen) == -1) {
860 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
873 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
874 if (client_write(outbuf, len) == -1) {
875 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
879 if (client_write(nl, nllen) == -1) {
880 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
888 if (client_write(outbuf, len) == -1) {
889 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
892 client_write(nl, nllen);
899 * Callback function for mime parser that simply lists the part
901 void list_this_part(char *name, char *filename, char *partnum, char *disp,
902 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
903 char *cbid, void *cbuserdata)
907 ma = (struct ma_info *)cbuserdata;
908 if (ma->is_ma == 0) {
909 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
923 * Callback function for multipart prefix
925 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
926 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
927 char *cbid, void *cbuserdata)
931 ma = (struct ma_info *)cbuserdata;
932 if (!strcasecmp(cbtype, "multipart/alternative")) {
936 if (ma->is_ma == 0) {
937 cprintf("pref=%s|%s\n", partnum, cbtype);
943 * Callback function for multipart sufffix
945 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
946 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
947 char *cbid, void *cbuserdata)
951 ma = (struct ma_info *)cbuserdata;
952 if (ma->is_ma == 0) {
953 cprintf("suff=%s|%s\n", partnum, cbtype);
955 if (!strcasecmp(cbtype, "multipart/alternative")) {
962 * Callback function for mime parser that opens a section for downloading
963 * we use serv_files function here:
965 extern void OpenCmdResult(char *filename, const char *mime_type);
966 void mime_download(char *name, char *filename, char *partnum, char *disp,
967 void *content, char *cbtype, char *cbcharset, size_t length,
968 char *encoding, char *cbid, void *cbuserdata)
972 /* Silently go away if there's already a download open. */
973 if (CC->download_fp != NULL)
977 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
978 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
980 CC->download_fp = tmpfile();
981 if (CC->download_fp == NULL) {
982 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
983 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
987 rv = fwrite(content, length, 1, CC->download_fp);
989 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
990 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
991 fclose(CC->download_fp);
992 CC->download_fp = NULL;
995 fflush(CC->download_fp);
996 rewind(CC->download_fp);
998 OpenCmdResult(filename, cbtype);
1004 * Callback function for mime parser that outputs a section all at once.
1005 * We can specify the desired section by part number *or* content-id.
1007 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1008 void *content, char *cbtype, char *cbcharset, size_t length,
1009 char *encoding, char *cbid, void *cbuserdata)
1011 int *found_it = (int *)cbuserdata;
1014 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1015 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1018 cprintf("%d %d|-1|%s|%s|%s\n",
1025 client_write(content, length);
1030 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
1031 struct CtdlMessage *ret = NULL;
1033 const char *upper_bound;
1035 cit_uint8_t field_header;
1039 upper_bound = Buffer + Length;
1044 // Parse the three bytes that begin EVERY message on disk.
1045 // The first is always 0xFF, the on-disk magic number.
1046 // The second is the anonymous/public type byte.
1047 // The third is the format type byte (vari, fixed, or MIME).
1051 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1054 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1055 memset(ret, 0, sizeof(struct CtdlMessage));
1057 ret->cm_magic = CTDLMESSAGE_MAGIC;
1058 ret->cm_anon_type = *mptr++; // Anon type byte
1059 ret->cm_format_type = *mptr++; // Format type byte
1061 // The rest is zero or more arbitrary fields. Load them in.
1062 // We're done when we encounter either a zero-length field or
1063 // have just processed the 'M' (message text) field.
1066 field_header = '\0';
1069 while (field_header == '\0') { // work around possibly buggy messages
1070 if (mptr >= upper_bound) {
1073 field_header = *mptr++;
1075 if (mptr >= upper_bound) {
1078 which = field_header;
1081 CM_SetField(ret, which, mptr);
1083 mptr += len + 1; // advance to next field
1085 } while ((mptr < upper_bound) && (field_header != 'M'));
1090 // Load a message from disk into memory.
1091 // This is used by CtdlOutputMsg() and other fetch functions.
1093 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1094 // using the CM_Free(); function.
1096 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1097 struct cdbdata dmsgtext;
1098 struct CtdlMessage *ret = NULL;
1100 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1101 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1102 if (dmsgtext.ptr == NULL) {
1103 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1107 if (dmsgtext.ptr[dmsgtext.len - 1] != '\0') {
1108 // FIXME LMDB cannot write to immutable memory
1109 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1110 dmsgtext.ptr[dmsgtext.len - 1] = '\0';
1113 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext.ptr, dmsgtext.len);
1118 // Always make sure there's something in the msg text field. If
1119 // it's NULL, the message text is most likely stored separately,
1120 // so go ahead and fetch that. Failing that, just set a dummy
1121 // body so other code doesn't barf.
1123 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1124 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1125 if (dmsgtext.ptr != NULL) {
1126 CM_SetField(ret, eMesageText, dmsgtext.ptr);
1129 if (CM_IsEmpty(ret, eMesageText)) {
1130 CM_SetField(ret, eMesageText, "\r\n\r\n (no text)\r\n");
1137 // Pre callback function for multipart/alternative
1139 // NOTE: this differs from the standard behavior for a reason. Normally when
1140 // displaying multipart/alternative you want to show the _last_ usable
1141 // format in the message. Here we show the _first_ one, because it's
1142 // usually text/plain. Since this set of functions is designed for text
1143 // output to non-MIME-aware clients, this is the desired behavior.
1145 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1146 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1147 char *cbid, void *cbuserdata)
1151 ma = (struct ma_info *)cbuserdata;
1152 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1153 if (!strcasecmp(cbtype, "multipart/alternative")) {
1157 if (!strcasecmp(cbtype, "message/rfc822")) {
1164 // Post callback function for multipart/alternative
1166 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1167 void *content, char *cbtype, char *cbcharset, size_t length,
1168 char *encoding, char *cbid, void *cbuserdata)
1172 ma = (struct ma_info *)cbuserdata;
1173 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1174 if (!strcasecmp(cbtype, "multipart/alternative")) {
1178 if (!strcasecmp(cbtype, "message/rfc822")) {
1184 // Inline callback function for mime parser that wants to display text
1185 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1186 void *content, char *cbtype, char *cbcharset, size_t length,
1187 char *encoding, char *cbid, void *cbuserdata)
1194 ma = (struct ma_info *)cbuserdata;
1197 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1198 partnum, filename, cbtype, (long)length
1201 // If we're in the middle of a multipart/alternative scope and
1202 // we've already printed another section, skip this one.
1203 if ( (ma->is_ma) && (ma->did_print) ) {
1204 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1209 if ( (!strcasecmp(cbtype, "text/plain"))
1210 || (IsEmptyStr(cbtype)) ) {
1213 client_write(wptr, length);
1214 if (wptr[length-1] != '\n') {
1221 if (!strcasecmp(cbtype, "text/html")) {
1222 ptr = html_to_ascii(content, length, 80, 0);
1224 client_write(ptr, wlen);
1225 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1232 if (ma->use_fo_hooks) {
1233 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1238 if (strncasecmp(cbtype, "multipart/", 10)) {
1239 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1240 partnum, filename, cbtype, (long)length);
1246 // The client is elegant and sophisticated and wants to be choosy about
1247 // MIME content types, so figure out which multipart/alternative part
1248 // we're going to send.
1250 // We use a system of weights. When we find a part that matches one of the
1251 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1252 // and then set ma->chosen_pref to that MIME type's position in our preference
1253 // list. If we then hit another match, we only replace the first match if
1254 // the preference value is lower.
1255 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1256 void *content, char *cbtype, char *cbcharset, size_t length,
1257 char *encoding, char *cbid, void *cbuserdata)
1263 ma = (struct ma_info *)cbuserdata;
1265 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1266 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1267 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1268 if (i < ma->chosen_pref) {
1269 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1270 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1271 ma->chosen_pref = i;
1278 // Now that we've chosen our preferred part, output it.
1279 void output_preferred(char *name,
1293 int add_newline = 0;
1296 char *decoded = NULL;
1297 size_t bytes_decoded;
1300 ma = (struct ma_info *)cbuserdata;
1302 // This is not the MIME part you're looking for...
1303 if (strcasecmp(partnum, ma->chosen_part)) return;
1305 // If the content-type of this part is in our preferred formats
1306 // list, we can simply output it verbatim.
1307 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1308 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1309 if (!strcasecmp(buf, cbtype)) {
1310 /* Yeah! Go! W00t!! */
1311 if (ma->dont_decode == 0)
1312 rc = mime_decode_now (content,
1318 break; // Give us the chance, maybe theres another one.
1320 if (rc == 0) text_content = (char *)content;
1322 text_content = decoded;
1323 length = bytes_decoded;
1326 if (text_content[length-1] != '\n') {
1329 cprintf("Content-type: %s", cbtype);
1330 if (!IsEmptyStr(cbcharset)) {
1331 cprintf("; charset=%s", cbcharset);
1333 cprintf("\nContent-length: %d\n",
1334 (int)(length + add_newline) );
1335 if (!IsEmptyStr(encoding)) {
1336 cprintf("Content-transfer-encoding: %s\n", encoding);
1339 cprintf("Content-transfer-encoding: 7bit\n");
1341 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1343 if (client_write(text_content, length) == -1)
1345 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1348 if (add_newline) cprintf("\n");
1349 if (decoded != NULL) free(decoded);
1354 // No translations required or possible: output as text/plain
1355 cprintf("Content-type: text/plain\n\n");
1357 if (ma->dont_decode == 0)
1358 rc = mime_decode_now (content,
1364 return; // Give us the chance, maybe theres another one.
1366 if (rc == 0) text_content = (char *)content;
1368 text_content = decoded;
1369 length = bytes_decoded;
1372 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1373 if (decoded != NULL) free(decoded);
1378 char desired_section[64];
1384 // Callback function
1385 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1386 void *content, char *cbtype, char *cbcharset, size_t length,
1387 char *encoding, char *cbid, void *cbuserdata)
1389 struct encapmsg *encap;
1391 encap = (struct encapmsg *)cbuserdata;
1393 // Only proceed if this is the desired section...
1394 if (!strcasecmp(encap->desired_section, partnum)) {
1395 encap->msglen = length;
1396 encap->msg = malloc(length + 2);
1397 memcpy(encap->msg, content, length);
1403 // Determine whether the specified message exists in the cached_msglist
1404 // (This is a security check)
1405 int check_cached_msglist(long msgnum) {
1407 // cases in which we skip the check
1408 if (!CC) return om_ok; // not a session
1409 if (CC->client_socket <= 0) return om_ok; // not a client session
1410 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1411 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1413 // Do a binary search within the cached_msglist for the requested msgnum
1415 int max = (CC->cached_num_msgs - 1);
1417 while (max >= min) {
1418 int middle = min + (max-min) / 2 ;
1419 if (msgnum == CC->cached_msglist[middle]) {
1422 if (msgnum > CC->cached_msglist[middle]) {
1430 return om_access_denied;
1434 // Get a message off disk. (returns om_* values found in msgbase.h)
1435 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1436 int mode, // how would you like that message?
1437 int headers_only, // eschew the message body?
1438 int do_proto, // do Citadel protocol responses?
1439 int crlf, // Use CRLF newlines instead of LF?
1440 char *section, // NULL or a message/rfc822 section
1441 int flags, // various flags; see msgbase.h
1446 struct CtdlMessage *TheMessage = NULL;
1447 int retcode = CIT_OK;
1448 struct encapmsg encap;
1451 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1453 (section ? section : "<>")
1456 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1459 if (r == om_not_logged_in) {
1460 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1463 cprintf("%d An unknown error has occurred.\n", ERROR);
1470 * Check to make sure the message is actually IN this room
1472 r = check_cached_msglist(msg_num);
1473 if (r == om_access_denied) {
1474 /* Not in the cache? We get ONE shot to check it again. */
1475 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1476 r = check_cached_msglist(msg_num);
1479 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1480 msg_num, CC->room.QRname
1483 if (r == om_access_denied) {
1484 cprintf("%d message %ld was not found in this room\n",
1485 ERROR + HIGHER_ACCESS_REQUIRED,
1494 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1495 * request that we don't even bother loading the body into memory.
1497 if (headers_only == HEADERS_FAST) {
1498 TheMessage = CtdlFetchMessage(msg_num, 0);
1501 TheMessage = CtdlFetchMessage(msg_num, 1);
1504 if (TheMessage == NULL) {
1505 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1506 ERROR + MESSAGE_NOT_FOUND, msg_num);
1507 return(om_no_such_msg);
1510 /* Here is the weird form of this command, to process only an
1511 * encapsulated message/rfc822 section.
1513 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1514 memset(&encap, 0, sizeof encap);
1515 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1516 mime_parser(CM_RANGE(TheMessage, eMesageText),
1517 *extract_encapsulated_message,
1518 NULL, NULL, (void *)&encap, 0
1521 if ((Author != NULL) && (*Author == NULL))
1524 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1526 if ((Address != NULL) && (*Address == NULL))
1529 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1531 if ((MessageID != NULL) && (*MessageID == NULL))
1534 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1536 CM_Free(TheMessage);
1540 encap.msg[encap.msglen] = 0;
1541 TheMessage = convert_internet_message(encap.msg);
1542 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1544 /* Now we let it fall through to the bottom of this
1545 * function, because TheMessage now contains the
1546 * encapsulated message instead of the top-level
1547 * message. Isn't that neat?
1552 cprintf("%d msg %ld has no part %s\n",
1553 ERROR + MESSAGE_NOT_FOUND,
1557 retcode = om_no_such_msg;
1562 /* Ok, output the message now */
1563 if (retcode == CIT_OK)
1564 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1565 if ((Author != NULL) && (*Author == NULL))
1568 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1570 if ((Address != NULL) && (*Address == NULL))
1573 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1575 if ((MessageID != NULL) && (*MessageID == NULL))
1578 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1581 CM_Free(TheMessage);
1587 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1590 char display_name[256];
1592 /* begin header processing loop for Citadel message format */
1593 safestrncpy(display_name, "<unknown>", sizeof display_name);
1594 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1595 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1596 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1597 safestrncpy(display_name, "****", sizeof display_name);
1599 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1600 safestrncpy(display_name, "anonymous", sizeof display_name);
1603 safestrncpy(display_name, buf, sizeof display_name);
1605 if ((is_room_aide())
1606 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1607 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1608 size_t tmp = strlen(display_name);
1609 snprintf(&display_name[tmp],
1610 sizeof display_name - tmp,
1615 /* Now spew the header fields in the order we like them. */
1616 for (i=0; i< NDiskFields; ++i) {
1618 Field = FieldOrder[i];
1619 if (Field != eMesageText) {
1620 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1621 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1622 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1624 if (Field == eAuthor) {
1626 cprintf("%s=%s\n", msgkeys[Field], display_name);
1629 /* Masquerade display name if needed */
1632 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1635 /* Give the client a hint about whether the message originated locally */
1636 if (Field == erFc822Addr) {
1637 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1638 cprintf("locl=yes\n"); // message originated locally.
1650 void OutputRFC822MsgHeaders(
1651 struct CtdlMessage *TheMessage,
1652 int flags, /* should the message be exported clean */
1653 const char *nl, int nlen,
1654 char *mid, long sizeof_mid,
1655 char *suser, long sizeof_suser,
1656 char *luser, long sizeof_luser,
1657 char *fuser, long sizeof_fuser,
1658 char *snode, long sizeof_snode)
1660 char datestamp[100];
1661 int subject_found = 0;
1668 for (i = 0; i < NDiskFields; ++i) {
1669 if (TheMessage->cm_fields[FieldOrder[i]]) {
1670 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1671 switch (FieldOrder[i]) {
1673 safestrncpy(luser, mptr, sizeof_luser);
1674 safestrncpy(suser, mptr, sizeof_suser);
1677 if ((flags & QP_EADDR) != 0) {
1678 mptr = qp_encode_email_addrs(mptr);
1680 sanitize_truncated_recipient(mptr);
1681 cprintf("CC: %s%s", mptr, nl);
1684 cprintf("Return-Path: %s%s", mptr, nl);
1687 cprintf("List-ID: %s%s", mptr, nl);
1690 if ((flags & QP_EADDR) != 0)
1691 mptr = qp_encode_email_addrs(mptr);
1693 while ((*hptr != '\0') && isspace(*hptr))
1695 if (!IsEmptyStr(hptr))
1696 cprintf("Envelope-To: %s%s", hptr, nl);
1699 cprintf("Subject: %s%s", mptr, nl);
1703 safestrncpy(mid, mptr, sizeof_mid);
1706 safestrncpy(fuser, mptr, sizeof_fuser);
1709 if (haschar(mptr, '@') == 0) {
1710 sanitize_truncated_recipient(mptr);
1711 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1715 if ((flags & QP_EADDR) != 0) {
1716 mptr = qp_encode_email_addrs(mptr);
1718 sanitize_truncated_recipient(mptr);
1719 cprintf("To: %s", mptr);
1724 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1725 cprintf("Date: %s%s", datestamp, nl);
1728 cprintf("References: ");
1729 k = num_tokens(mptr, '|');
1730 for (j=0; j<k; ++j) {
1731 extract_token(buf, mptr, j, '|', sizeof buf);
1732 cprintf("<%s>", buf);
1743 while ((*hptr != '\0') && isspace(*hptr))
1745 if (!IsEmptyStr(hptr))
1746 cprintf("Reply-To: %s%s", mptr, nl);
1758 /* these don't map to mime message headers. */
1761 if (mptr != mpptr) {
1766 if (subject_found == 0) {
1767 cprintf("Subject: (no subject)%s", nl);
1772 void Dump_RFC822HeadersBody(
1773 struct CtdlMessage *TheMessage,
1774 int headers_only, /* eschew the message body? */
1775 int flags, /* should the bessage be exported clean? */
1776 const char *nl, int nlen)
1778 cit_uint8_t prev_ch;
1780 const char *StartOfText = StrBufNOTNULL;
1783 int nllen = strlen(nl);
1787 mptr = TheMessage->cm_fields[eMesageText];
1790 while (*mptr != '\0') {
1791 if (*mptr == '\r') {
1798 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1800 eoh = *(mptr+1) == '\n';
1804 StartOfText = strchr(StartOfText, '\n');
1805 StartOfText = strchr(StartOfText, '\n');
1808 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1809 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1810 ((headers_only != HEADERS_NONE) &&
1811 (headers_only != HEADERS_ONLY))
1813 if (*mptr == '\n') {
1814 memcpy(&outbuf[outlen], nl, nllen);
1816 outbuf[outlen] = '\0';
1819 outbuf[outlen++] = *mptr;
1823 if (flags & ESC_DOT) {
1824 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1825 outbuf[outlen++] = '.';
1830 if (outlen > 1000) {
1831 if (client_write(outbuf, outlen) == -1) {
1832 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1835 lfSent = (outbuf[outlen - 1] == '\n');
1840 client_write(outbuf, outlen);
1841 lfSent = (outbuf[outlen - 1] == '\n');
1844 client_write(nl, nlen);
1848 /* If the format type on disk is 1 (fixed-format), then we want
1849 * everything to be output completely literally ... regardless of
1850 * what message transfer format is in use.
1852 void DumpFormatFixed(
1853 struct CtdlMessage *TheMessage,
1854 int mode, /* how would you like that message? */
1855 const char *nl, int nllen)
1863 mptr = TheMessage->cm_fields[eMesageText];
1865 if (mode == MT_MIME) {
1866 cprintf("Content-type: text/plain\n\n");
1870 while (ch = *mptr++, ch > 0) {
1874 if ((buflen > 250) && (!xlline)){
1878 while ((buflen > 0) &&
1879 (!isspace(buf[buflen])))
1885 mptr -= tbuflen - buflen;
1891 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1892 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1897 memcpy (&buf[buflen], nl, nllen);
1901 if (client_write(buf, buflen) == -1) {
1902 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1914 if (!IsEmptyStr(buf)) {
1915 cprintf("%s%s", buf, nl);
1921 * Get a message off disk. (returns om_* values found in msgbase.h)
1923 int CtdlOutputPreLoadedMsg(
1924 struct CtdlMessage *TheMessage,
1925 int mode, /* how would you like that message? */
1926 int headers_only, /* eschew the message body? */
1927 int do_proto, /* do Citadel protocol responses? */
1928 int crlf, /* Use CRLF newlines instead of LF? */
1929 int flags /* should the bessage be exported clean? */
1932 const char *nl; /* newline string */
1936 /* Buffers needed for RFC822 translation. These are all filled
1937 * using functions that are bounds-checked, and therefore we can
1938 * make them substantially smaller than SIZ.
1946 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1947 ((TheMessage == NULL) ? "NULL" : "not null"),
1948 mode, headers_only, do_proto, crlf
1951 strcpy(mid, "unknown");
1952 nl = (crlf ? "\r\n" : "\n");
1953 nlen = crlf ? 2 : 1;
1955 if (!CM_IsValidMsg(TheMessage)) {
1956 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
1957 return(om_no_such_msg);
1960 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1961 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1963 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
1964 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
1967 /* Are we downloading a MIME component? */
1968 if (mode == MT_DOWNLOAD) {
1969 if (TheMessage->cm_format_type != FMT_RFC822) {
1971 cprintf("%d This is not a MIME message.\n",
1972 ERROR + ILLEGAL_VALUE);
1973 } else if (CC->download_fp != NULL) {
1974 if (do_proto) cprintf(
1975 "%d You already have a download open.\n",
1976 ERROR + RESOURCE_BUSY);
1978 /* Parse the message text component */
1979 mime_parser(CM_RANGE(TheMessage, eMesageText),
1980 *mime_download, NULL, NULL, NULL, 0);
1981 /* If there's no file open by this time, the requested
1982 * section wasn't found, so print an error
1984 if (CC->download_fp == NULL) {
1985 if (do_proto) cprintf(
1986 "%d Section %s not found.\n",
1987 ERROR + FILE_NOT_FOUND,
1988 CC->download_desired_section);
1991 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1994 // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1995 // in a single server operation instead of opening a download file.
1996 if (mode == MT_SPEW_SECTION) {
1997 if (TheMessage->cm_format_type != FMT_RFC822) {
1999 cprintf("%d This is not a MIME message.\n",
2000 ERROR + ILLEGAL_VALUE);
2003 // Locate and parse the component specified by the caller
2005 mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2007 // If section wasn't found, print an error
2010 cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
2014 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2017 // now for the user-mode message reading loops
2018 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2020 // Does the caller want to skip the headers?
2021 if (headers_only == HEADERS_NONE) goto START_TEXT;
2023 // Tell the client which format type we're using.
2024 if ( (mode == MT_CITADEL) && (do_proto) ) {
2025 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2028 // nhdr=yes means that we're only displaying headers, no body
2029 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2030 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2033 cprintf("nhdr=yes\n");
2036 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2037 OutputCtdlMsgHeaders(TheMessage, do_proto);
2040 // begin header processing loop for RFC822 transfer format
2045 if (mode == MT_RFC822)
2046 OutputRFC822MsgHeaders(
2051 suser, sizeof(suser),
2052 luser, sizeof(luser),
2053 fuser, sizeof(fuser),
2054 snode, sizeof(snode)
2058 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2059 suser[i] = tolower(suser[i]);
2060 if (!isalnum(suser[i])) suser[i]='_';
2063 if (mode == MT_RFC822) {
2064 /* Construct a fun message id */
2065 cprintf("Message-ID: <%s", mid);
2066 if (strchr(mid, '@')==NULL) {
2067 cprintf("@%s", snode);
2071 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2072 cprintf("From: \"----\" <x@x.org>%s", nl);
2074 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2075 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2077 else if (!IsEmptyStr(fuser)) {
2078 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2081 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2084 /* Blank line signifying RFC822 end-of-headers */
2085 if (TheMessage->cm_format_type != FMT_RFC822) {
2090 // end header processing loop ... at this point, we're in the text
2092 if (headers_only == HEADERS_FAST) goto DONE;
2094 // Tell the client about the MIME parts in this message
2095 if (TheMessage->cm_format_type == FMT_RFC822) {
2096 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2097 memset(&ma, 0, sizeof(struct ma_info));
2098 mime_parser(CM_RANGE(TheMessage, eMesageText),
2099 (do_proto ? *list_this_part : NULL),
2100 (do_proto ? *list_this_pref : NULL),
2101 (do_proto ? *list_this_suff : NULL),
2104 else if (mode == MT_RFC822) { // unparsed RFC822 dump
2105 Dump_RFC822HeadersBody(
2114 if (headers_only == HEADERS_ONLY) {
2118 // signify start of msg text
2119 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2120 if (do_proto) cprintf("text\n");
2123 if (TheMessage->cm_format_type == FMT_FIXED)
2126 mode, // how would you like that message?
2129 // If the message on disk is format 0 (Citadel vari-format), we
2130 // output using the formatter at 80 columns. This is the final output
2131 // form if the transfer format is RFC822, but if the transfer format
2132 // is Citadel proprietary, it'll still work, because the indentation
2133 // for new paragraphs is correct and the client will reformat the
2134 // message to the reader's screen width.
2136 if (TheMessage->cm_format_type == FMT_CITADEL) {
2137 if (mode == MT_MIME) {
2138 cprintf("Content-type: text/x-citadel-variformat\n\n");
2140 memfmout(TheMessage->cm_fields[eMesageText], nl);
2143 // If the message on disk is format 4 (MIME), we've gotta hand it
2144 // off to the MIME parser. The client has already been told that
2145 // this message is format 1 (fixed format), so the callback function
2146 // we use will display those parts as-is.
2148 if (TheMessage->cm_format_type == FMT_RFC822) {
2149 memset(&ma, 0, sizeof(struct ma_info));
2151 if (mode == MT_MIME) {
2152 ma.use_fo_hooks = 0;
2153 strcpy(ma.chosen_part, "1");
2154 ma.chosen_pref = 9999;
2155 ma.dont_decode = CC->msg4_dont_decode;
2156 mime_parser(CM_RANGE(TheMessage, eMesageText),
2157 *choose_preferred, *fixed_output_pre,
2158 *fixed_output_post, (void *)&ma, 1);
2159 mime_parser(CM_RANGE(TheMessage, eMesageText),
2160 *output_preferred, NULL, NULL, (void *)&ma, 1);
2163 ma.use_fo_hooks = 1;
2164 mime_parser(CM_RANGE(TheMessage, eMesageText),
2165 *fixed_output, *fixed_output_pre,
2166 *fixed_output_post, (void *)&ma, 0);
2171 DONE: /* now we're done */
2172 if (do_proto) cprintf("000\n");
2176 // Save one or more message pointers into a specified room
2177 // (Returns 0 for success, nonzero for failure)
2178 // roomname may be NULL to use the current room
2180 // Note that the 'supplied_msg' field may be set to NULL, in which case
2181 // the message will be fetched from disk, by number, if we need to perform
2182 // replication checks. This adds an additional database read, so if the
2183 // caller already has the message in memory then it should be supplied. (Obviously
2184 // this mode of operation only works if we're saving a single message.)
2186 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2187 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2190 char hold_rm[ROOMNAMELEN];
2191 struct cdbdata *cdbfr;
2194 long highest_msg = 0L;
2197 struct CtdlMessage *msg = NULL;
2199 long *msgs_to_be_merged = NULL;
2200 int num_msgs_to_be_merged = 0;
2203 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2204 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2207 strcpy(hold_rm, CC->room.QRname);
2210 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2211 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2212 if (num_newmsgs > 1) supplied_msg = NULL;
2214 /* Now the regular stuff */
2215 if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
2216 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2217 return(ERROR + ROOM_NOT_FOUND);
2220 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2221 num_msgs_to_be_merged = 0;
2222 num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
2224 /* Create a list of msgid's which were supplied by the caller, but do
2225 * not already exist in the target room. It is absolutely taboo to
2226 * have more than one reference to the same message in a room.
2228 for (i=0; i<num_newmsgs; ++i) {
2230 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2231 if (msglist[j] == newmsgidlist[i]) {
2236 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2240 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2243 * Now merge the new messages
2245 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2246 if (msglist == NULL) {
2247 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2248 free(msgs_to_be_merged);
2249 abort(); // FIXME FIXME FOOFOO
2250 return (ERROR + INTERNAL_ERROR);
2252 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2253 num_msgs += num_msgs_to_be_merged;
2255 /* Sort the message list, so all the msgid's are in order */
2256 num_msgs = sort_msglist(msglist, num_msgs);
2258 /* Determine the highest message number */
2259 highest_msg = msglist[num_msgs - 1];
2261 /* Write it back to disk. */
2262 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
2264 /* Free up the memory we used. */
2267 /* Update the highest-message pointer and unlock the room. */
2268 CC->room.QRhighest = highest_msg;
2269 CtdlPutRoomLock(&CC->room);
2271 /* Perform replication checks if necessary */
2272 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2273 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2275 for (i=0; i<num_msgs_to_be_merged; ++i) {
2276 msgid = msgs_to_be_merged[i];
2278 if (supplied_msg != NULL) {
2282 msg = CtdlFetchMessage(msgid, 0);
2286 ReplicationChecks(msg);
2288 /* If the message has an Exclusive ID, index that... */
2289 if (!CM_IsEmpty(msg, eExclusiveID)) {
2290 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2293 /* Free up the memory we may have allocated */
2294 if (msg != supplied_msg) {
2303 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2306 /* Submit this room for processing by hooks */
2307 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2308 if (total_roomhook_errors) {
2309 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2312 /* Go back to the room we were in before we wandered here... */
2313 CtdlGetRoom(&CC->room, hold_rm);
2315 /* Bump the reference count for all messages which were merged */
2316 if (!suppress_refcount_adj) {
2317 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2320 /* Free up memory... */
2321 if (msgs_to_be_merged != NULL) {
2322 free(msgs_to_be_merged);
2325 /* Return success. */
2331 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2334 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
2335 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2340 * Message base operation to save a new message to the message store
2341 * (returns new message number)
2343 * This is the back end for CtdlSubmitMsg() and should not be directly
2344 * called by server-side modules.
2347 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2355 * If the message is big, set its body aside for storage elsewhere
2356 * and we hide the message body from the serializer
2358 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2360 holdM = msg->cm_fields[eMesageText];
2361 msg->cm_fields[eMesageText] = NULL;
2362 holdMLen = msg->cm_lengths[eMesageText];
2363 msg->cm_lengths[eMesageText] = 0;
2366 /* Serialize our data structure for storage in the database */
2367 CtdlSerializeMessage(&smr, msg);
2370 /* put the message body back into the message */
2371 msg->cm_fields[eMesageText] = holdM;
2372 msg->cm_lengths[eMesageText] = holdMLen;
2377 cprintf("%d Unable to serialize message\n",
2378 ERROR + INTERNAL_ERROR);
2381 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2387 /* Write our little bundle of joy into the message base */
2388 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2390 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2394 retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
2396 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2401 /* Free the memory we used for the serialized message */
2407 long send_message(struct CtdlMessage *msg) {
2413 /* Get a new message number */
2414 newmsgid = get_new_message_number();
2416 /* Generate an ID if we don't have one already */
2417 if (CM_IsEmpty(msg, emessageId)) {
2418 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2419 (long unsigned int) time(NULL),
2420 (long unsigned int) newmsgid,
2421 CtdlGetConfigStr("c_fqdn")
2424 CM_SetField(msg, emessageId, msgidbuf);
2427 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2433 /* Return the *local* message ID to the caller
2434 * (even if we're storing an incoming network message)
2441 * Serialize a struct CtdlMessage into the format used on disk.
2443 * This function loads up a "struct ser_ret" (defined in server.h) which
2444 * contains the length of the serialized message and a pointer to the
2445 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2447 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2448 struct CtdlMessage *msg) /* unserialized msg */
2454 * Check for valid message format
2456 if (CM_IsValidMsg(msg) == 0) {
2457 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2464 for (i=0; i < NDiskFields; ++i)
2465 if (msg->cm_fields[FieldOrder[i]] != NULL)
2466 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2468 ret->ser = malloc(ret->len);
2469 if (ret->ser == NULL) {
2470 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2477 ret->ser[1] = msg->cm_anon_type;
2478 ret->ser[2] = msg->cm_format_type;
2481 for (i=0; i < NDiskFields; ++i) {
2482 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2483 ret->ser[wlen++] = (char)FieldOrder[i];
2485 memcpy(&ret->ser[wlen],
2486 msg->cm_fields[FieldOrder[i]],
2487 msg->cm_lengths[FieldOrder[i]] + 1);
2489 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2493 if (ret->len != wlen) {
2494 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2502 * Check to see if any messages already exist in the current room which
2503 * carry the same Exclusive ID as this one. If any are found, delete them.
2505 void ReplicationChecks(struct CtdlMessage *msg) {
2506 long old_msgnum = (-1L);
2508 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2510 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2512 /* No exclusive id? Don't do anything. */
2513 if (msg == NULL) return;
2514 if (CM_IsEmpty(msg, eExclusiveID)) return;
2516 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2517 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2519 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2520 if (old_msgnum > 0L) {
2521 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2522 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2528 * Save a message to disk and submit it into the delivery system.
2530 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2531 struct recptypes *recps, /* recipients (if mail) */
2532 const char *force /* force a particular room? */
2534 char hold_rm[ROOMNAMELEN];
2535 char actual_rm[ROOMNAMELEN];
2536 char force_room[ROOMNAMELEN];
2537 char content_type[SIZ]; /* We have to learn this */
2538 char recipient[SIZ];
2539 char bounce_to[1024];
2542 const char *mptr = NULL;
2543 struct ctdluser userbuf;
2545 struct MetaData smi;
2546 char *collected_addresses = NULL;
2547 struct addresses_to_be_filed *aptr = NULL;
2548 StrBuf *saved_rfc822_version = NULL;
2549 int qualified_for_journaling = 0;
2551 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2552 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2554 /* If this message has no timestamp, we take the liberty of
2555 * giving it one, right now.
2557 if (CM_IsEmpty(msg, eTimestamp)) {
2558 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2561 /* If this message has no path, we generate one.
2563 if (CM_IsEmpty(msg, eMessagePath)) {
2564 if (!CM_IsEmpty(msg, eAuthor)) {
2565 CM_CopyField(msg, eMessagePath, eAuthor);
2566 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2567 if (isspace(msg->cm_fields[eMessagePath][a])) {
2568 msg->cm_fields[eMessagePath][a] = ' ';
2573 CM_SetField(msg, eMessagePath, "unknown");
2577 if (force == NULL) {
2578 force_room[0] = '\0';
2581 strcpy(force_room, force);
2584 /* Learn about what's inside, because it's what's inside that counts */
2585 if (CM_IsEmpty(msg, eMesageText)) {
2586 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2590 switch (msg->cm_format_type) {
2592 strcpy(content_type, "text/x-citadel-variformat");
2595 strcpy(content_type, "text/plain");
2598 strcpy(content_type, "text/plain");
2599 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2602 safestrncpy(content_type, &mptr[13], sizeof content_type);
2603 string_trim(content_type);
2604 aptr = content_type;
2605 while (!IsEmptyStr(aptr)) {
2617 /* Goto the correct room */
2618 room = (recps) ? CC->room.QRname : SENTITEMS;
2619 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2620 strcpy(hold_rm, CC->room.QRname);
2621 strcpy(actual_rm, CC->room.QRname);
2622 if (recps != NULL) {
2623 strcpy(actual_rm, SENTITEMS);
2626 /* If the user is a twit, move to the twit room for posting */
2628 if (CC->user.axlevel == AxProbU) {
2629 strcpy(hold_rm, actual_rm);
2630 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2631 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2635 /* ...or if this message is destined for Aide> then go there. */
2636 if (!IsEmptyStr(force_room)) {
2637 strcpy(actual_rm, force_room);
2640 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2641 if (strcasecmp(actual_rm, CC->room.QRname)) {
2642 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2646 * If this message has no O (room) field, generate one.
2648 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2649 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
2652 /* Perform "before save" hooks (aborting if any return nonzero) */
2653 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2654 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2657 * If this message has an Exclusive ID, and the room is replication
2658 * checking enabled, then do replication checks.
2660 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2661 ReplicationChecks(msg);
2664 /* Save it to disk */
2665 syslog(LOG_DEBUG, "msgbase: saving to disk");
2666 newmsgid = send_message(msg);
2667 if (newmsgid <= 0L) return(-5);
2669 /* Write a supplemental message info record. This doesn't have to
2670 * be a critical section because nobody else knows about this message
2673 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2674 memset(&smi, 0, sizeof(struct MetaData));
2675 smi.meta_msgnum = newmsgid;
2676 smi.meta_refcount = 0;
2677 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2680 * Measure how big this message will be when rendered as RFC822.
2681 * We do this for two reasons:
2682 * 1. We need the RFC822 length for the new metadata record, so the
2683 * POP and IMAP services don't have to calculate message lengths
2684 * while the user is waiting (multiplied by potentially hundreds
2685 * or thousands of messages).
2686 * 2. If journaling is enabled, we will need an RFC822 version of the
2687 * message to attach to the journalized copy.
2689 if (CC->redirect_buffer != NULL) {
2690 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2691 exit(CTDLEXIT_REDIRECT);
2693 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2694 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2695 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2696 saved_rfc822_version = CC->redirect_buffer;
2697 CC->redirect_buffer = NULL;
2701 /* Now figure out where to store the pointers */
2702 syslog(LOG_DEBUG, "msgbase: storing pointers");
2704 /* If this is being done by the networker delivering a private
2705 * message, we want to BYPASS saving the sender's copy (because there
2706 * is no local sender; it would otherwise go to the Trashcan).
2708 if ((!CC->internal_pgm) || (recps == NULL)) {
2709 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2710 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2711 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2715 /* For internet mail, drop a copy in the outbound queue room */
2716 if ((recps != NULL) && (recps->num_internet > 0)) {
2717 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2720 /* If other rooms are specified, drop them there too. */
2721 if ((recps != NULL) && (recps->num_room > 0)) {
2722 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2723 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2724 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2725 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2729 /* Decide where bounces need to be delivered */
2730 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2731 if (CC->logged_in) {
2732 strcpy(bounce_to, CC->user.fullname);
2734 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2735 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2737 recps->bounce_to = bounce_to;
2740 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2742 // If this is private, local mail, make a copy in the recipient's mailbox and bump the reference count.
2743 if ((recps != NULL) && (recps->num_local > 0)) {
2747 pch = recps->recp_local;
2748 recps->recp_local = recipient;
2749 ntokens = num_tokens(pch, '|');
2750 for (i=0; i<ntokens; ++i) {
2751 extract_token(recipient, pch, i, '|', sizeof recipient);
2752 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2753 if (CtdlGetUser(&userbuf, recipient) == 0) {
2754 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2755 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2756 CtdlBumpNewMailCounter(userbuf.usernum); // if this user is logged in, tell them they have new mail.
2757 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2760 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2761 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2764 recps->recp_local = pch;
2767 /* Perform "after save" hooks */
2768 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2770 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2771 CM_FlushField(msg, eVltMsgNum);
2773 /* Go back to the room we started from */
2774 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2775 if (strcasecmp(hold_rm, CC->room.QRname)) {
2776 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2780 * Any addresses to harvest for someone's address book?
2782 if ( (CC->logged_in) && (recps != NULL) ) {
2783 collected_addresses = harvest_collected_addresses(msg);
2786 if (collected_addresses != NULL) {
2787 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2788 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2789 aptr->roomname = strdup(actual_rm);
2790 aptr->collected_addresses = collected_addresses;
2791 begin_critical_section(S_ATBF);
2794 end_critical_section(S_ATBF);
2798 * Determine whether this message qualifies for journaling.
2800 if (!CM_IsEmpty(msg, eJournal)) {
2801 qualified_for_journaling = 0;
2804 if (recps == NULL) {
2805 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2807 else if (recps->num_local + recps->num_internet > 0) {
2808 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2811 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2816 * Do we have to perform journaling? If so, hand off the saved
2817 * RFC822 version will be handed off to the journaler for background
2818 * submit. Otherwise, we have to free the memory ourselves.
2820 if (saved_rfc822_version != NULL) {
2821 if (qualified_for_journaling) {
2822 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2825 FreeStrBuf(&saved_rfc822_version);
2829 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2830 recps->bounce_to = NULL;
2838 * Convenience function for generating small administrative messages.
2840 long quickie_message(char *from,
2848 struct CtdlMessage *msg;
2849 struct recptypes *recp = NULL;
2851 msg = malloc(sizeof(struct CtdlMessage));
2852 memset(msg, 0, sizeof(struct CtdlMessage));
2853 msg->cm_magic = CTDLMESSAGE_MAGIC;
2854 msg->cm_anon_type = MES_NORMAL;
2855 msg->cm_format_type = format_type;
2857 if (!IsEmptyStr(from)) {
2858 CM_SetField(msg, eAuthor, from);
2860 else if (!IsEmptyStr(fromaddr)) {
2862 CM_SetField(msg, eAuthor, fromaddr);
2863 pAt = strchr(msg->cm_fields[eAuthor], '@');
2865 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2869 msg->cm_fields[eAuthor] = strdup("Citadel");
2872 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr);
2873 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
2874 if (!IsEmptyStr(to)) {
2875 CM_SetField(msg, eRecipient, to);
2876 recp = validate_recipients(to, NULL, 0);
2878 if (!IsEmptyStr(subject)) {
2879 CM_SetField(msg, eMsgSubject, subject);
2881 if (!IsEmptyStr(text)) {
2882 CM_SetField(msg, eMesageText, text);
2885 long msgnum = CtdlSubmitMsg(msg, recp, room);
2887 if (recp != NULL) free_recipients(recp);
2893 * Back end function used by CtdlMakeMessage() and similar functions
2895 StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
2897 size_t maxlen, // maximum message length
2898 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2899 int crlf // CRLF newlines instead of LF
2907 LineBuf = NewStrBufPlain(NULL, SIZ);
2908 if (exist == NULL) {
2909 Message = NewStrBufPlain(NULL, 4 * SIZ);
2912 Message = NewStrBufDup(exist);
2915 /* Do we need to change leading ".." to "." for SMTP escaping? */
2916 if ((tlen == 1) && (*terminator == '.')) {
2920 /* read in the lines of message text one by one */
2922 if (CtdlClientGetLine(LineBuf) < 0) {
2925 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
2928 if ( (!flushing) && (!finished) ) {
2930 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
2933 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
2936 /* Unescape SMTP-style input of two dots at the beginning of the line */
2937 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
2938 StrBufCutLeft(LineBuf, 1);
2940 StrBufAppendBuf(Message, LineBuf, 0);
2943 /* if we've hit the max msg length, flush the rest */
2944 if (StrLength(Message) >= maxlen) {
2948 } while (!finished);
2949 FreeStrBuf(&LineBuf);
2952 syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
2959 // Back end function used by CtdlMakeMessage() and similar functions
2960 char *CtdlReadMessageBody(char *terminator, // token signalling EOT
2962 size_t maxlen, // maximum message length
2963 StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
2964 int crlf // CRLF newlines instead of LF
2968 Message = CtdlReadMessageBodyBuf(terminator,
2974 if (Message == NULL) {
2978 return SmashStrBuf(&Message);
2983 struct CtdlMessage *CtdlMakeMessage(
2984 struct ctdluser *author, /* author's user structure */
2985 char *recipient, /* NULL if it's not mail */
2986 char *recp_cc, /* NULL if it's not mail */
2987 char *room, /* room where it's going */
2988 int type, /* see MES_ types in header file */
2989 int format_type, /* variformat, plain text, MIME... */
2990 char *fake_name, /* who we're masquerading as */
2991 char *my_email, /* which of my email addresses to use (empty is ok) */
2992 char *subject, /* Subject (optional) */
2993 char *supplied_euid, /* ...or NULL if this is irrelevant */
2994 char *preformatted_text, /* ...or NULL to read text from client */
2995 char *references /* Thread references */
2997 return CtdlMakeMessageLen(
2998 author, /* author's user structure */
2999 recipient, /* NULL if it's not mail */
3000 (recipient)?strlen(recipient) : 0,
3001 recp_cc, /* NULL if it's not mail */
3002 (recp_cc)?strlen(recp_cc): 0,
3003 room, /* room where it's going */
3004 (room)?strlen(room): 0,
3005 type, /* see MES_ types in header file */
3006 format_type, /* variformat, plain text, MIME... */
3007 fake_name, /* who we're masquerading as */
3008 (fake_name)?strlen(fake_name): 0,
3009 my_email, /* which of my email addresses to use (empty is ok) */
3010 (my_email)?strlen(my_email): 0,
3011 subject, /* Subject (optional) */
3012 (subject)?strlen(subject): 0,
3013 supplied_euid, /* ...or NULL if this is irrelevant */
3014 (supplied_euid)?strlen(supplied_euid):0,
3015 preformatted_text, /* ...or NULL to read text from client */
3016 (preformatted_text)?strlen(preformatted_text) : 0,
3017 references, /* Thread references */
3018 (references)?strlen(references):0);
3024 * Build a binary message to be saved on disk.
3025 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3026 * will become part of the message. This means you are no longer
3027 * responsible for managing that memory -- it will be freed along with
3028 * the rest of the fields when CM_Free() is called.)
3030 struct CtdlMessage *CtdlMakeMessageLen(
3031 struct ctdluser *author, /* author's user structure */
3032 char *recipient, /* NULL if it's not mail */
3034 char *recp_cc, /* NULL if it's not mail */
3036 char *room, /* room where it's going */
3038 int type, /* see MES_ types in header file */
3039 int format_type, /* variformat, plain text, MIME... */
3040 char *fake_name, /* who we're masquerading as */
3042 char *my_email, /* which of my email addresses to use (empty is ok) */
3044 char *subject, /* Subject (optional) */
3046 char *supplied_euid, /* ...or NULL if this is irrelevant */
3048 char *preformatted_text, /* ...or NULL to read text from client */
3050 char *references, /* Thread references */
3055 struct CtdlMessage *msg;
3057 StrBuf *FakeEncAuthor = NULL;
3059 msg = malloc(sizeof(struct CtdlMessage));
3060 memset(msg, 0, sizeof(struct CtdlMessage));
3061 msg->cm_magic = CTDLMESSAGE_MAGIC;
3062 msg->cm_anon_type = type;
3063 msg->cm_format_type = format_type;
3065 if (recipient != NULL) rcplen = string_trim(recipient);
3066 if (recp_cc != NULL) cclen = string_trim(recp_cc);
3068 /* Path or Return-Path */
3070 CM_SetField(msg, eMessagePath, my_email);
3072 else if (!IsEmptyStr(author->fullname)) {
3073 CM_SetField(msg, eMessagePath, author->fullname);
3075 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3077 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3078 CM_SetField(msg, eTimestamp, buf);
3081 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3084 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3086 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3087 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3088 FreeStrBuf(&FakeAuthor);
3090 if (!!IsEmptyStr(CC->room.QRname)) {
3091 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3092 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11]);
3095 CM_SetField(msg, eOriginalRoom, CC->room.QRname);
3100 CM_SetField(msg, eRecipient, recipient);
3103 CM_SetField(msg, eCarbonCopY, recp_cc);
3107 CM_SetField(msg, erFc822Addr, my_email);
3109 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3110 CM_SetField(msg, erFc822Addr, CC->cs_inet_email);
3113 if (subject != NULL) {
3115 length = string_trim(subject);
3121 while ((subject[i] != '\0') &&
3122 (IsAscii = isascii(subject[i]) != 0 ))
3125 CM_SetField(msg, eMsgSubject, subject);
3126 else /* ok, we've got utf8 in the string. */
3129 rfc2047Subj = rfc2047encode(subject, length);
3130 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3137 CM_SetField(msg, eExclusiveID, supplied_euid);
3141 CM_SetField(msg, eWeferences, references);
3144 if (preformatted_text != NULL) {
3145 CM_SetField(msg, eMesageText, preformatted_text);
3149 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3150 if (MsgBody != NULL) {
3151 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3160 * API function to delete messages which match a set of criteria
3161 * (returns the actual number of messages deleted)
3163 int CtdlDeleteMessages(const char *room_name, // which room
3164 long *dmsgnums, // array of msg numbers to be deleted
3165 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3166 char *content_type // or "" for any. regular expressions expected.
3168 struct ctdlroom qrbuf;
3169 struct cdbdata *cdbfr;
3170 long *msglist = NULL;
3171 long *dellist = NULL;
3174 int num_deleted = 0;
3176 struct MetaData smi;
3179 int need_to_free_re = 0;
3181 if (content_type) if (!IsEmptyStr(content_type)) {
3182 regcomp(&re, content_type, 0);
3183 need_to_free_re = 1;
3185 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3187 /* get room record, obtaining a lock... */
3188 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3189 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3190 if (need_to_free_re) regfree(&re);
3191 return(0); /* room not found */
3194 num_msgs = CtdlFetchMsgList(qrbuf.QRnumber, &msglist);
3196 dellist = malloc(num_msgs * sizeof(long));
3197 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3198 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3199 int have_more_del = 1;
3201 num_msgs = sort_msglist(msglist, num_msgs);
3202 if (num_dmsgnums > 1) {
3203 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3207 while ((i < num_msgs) && (have_more_del)) {
3210 /* Set/clear a bit for each criterion */
3212 /* 0 messages in the list or a null list means that we are
3213 * interested in deleting any messages which meet the other criteria.
3216 delete_this |= 0x01;
3219 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3224 if (msglist[i] == dmsgnums[j]) {
3225 delete_this |= 0x01;
3228 have_more_del = (j < num_dmsgnums);
3231 if (have_contenttype) {
3232 GetMetaData(&smi, msglist[i]);
3233 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3234 delete_this |= 0x02;
3237 delete_this |= 0x02;
3240 /* Delete message only if all bits are set */
3241 if (delete_this == 0x03) {
3242 dellist[num_deleted++] = msglist[i];
3248 num_msgs = sort_msglist(msglist, num_msgs);
3249 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long)));
3252 qrbuf.QRhighest = msglist[num_msgs - 1];
3255 qrbuf.QRhighest = 0;
3258 CtdlPutRoomLock(&qrbuf);
3260 /* Go through the messages we pulled out of the index, and decrement
3261 * their reference counts by 1. If this is the only room the message
3262 * was in, the reference count will reach zero and the message will
3263 * automatically be deleted from the database. We do this in a
3264 * separate pass because there might be plug-in hooks getting called,
3265 * and we don't want that happening during an S_ROOMS critical
3269 for (i=0; i<num_deleted; ++i) {
3270 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3272 AdjRefCountList(dellist, num_deleted, -1);
3274 /* Now free the memory we used, and go away. */
3275 if (msglist != NULL) free(msglist);
3276 if (dellist != NULL) free(dellist);
3277 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3278 if (need_to_free_re) regfree(&re);
3279 return (num_deleted);
3284 * GetMetaData() - Get the supplementary record for a message
3286 void GetMetaData(struct MetaData *smibuf, long msgnum) {
3287 struct cdbdata cdbsmi;
3290 memset(smibuf, 0, sizeof(struct MetaData));
3291 smibuf->meta_msgnum = msgnum;
3292 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3294 /* Use the negative of the message number for its supp record index */
3295 TheIndex = (0L - msgnum);
3297 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3298 if (cdbsmi.ptr == NULL) {
3299 return; /* record not found; leave it alone */
3301 memcpy(smibuf, cdbsmi.ptr, ((cdbsmi.len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi.len));
3307 * PutMetaData() - (re)write supplementary record for a message
3309 void PutMetaData(struct MetaData *smibuf)
3313 /* Use the negative of the message number for the metadata db index */
3314 TheIndex = (0L - smibuf->meta_msgnum);
3316 cdb_store(CDB_MSGMAIN,
3317 &TheIndex, (int)sizeof(long),
3318 smibuf, (int)sizeof(struct MetaData)
3323 // Convenience function to process a big block of AdjRefCount() operations
3324 void AdjRefCountList(long *msgnum, long nmsg, int incr) {
3327 for (i = 0; i < nmsg; i++) {
3328 AdjRefCount(msgnum[i], incr);
3333 // AdjRefCount - adjust the reference count for a message.
3334 // We need to delete from disk any message whose reference count reaches zero.
3335 void AdjRefCount(long msgnum, int incr) {
3336 struct MetaData smi;
3339 // This is a *tight* critical section; please keep it that way, as
3340 // it may get called while nested in other critical sections.
3341 // Complicating this any further will surely cause deadlock!
3342 begin_critical_section(S_SUPPMSGMAIN);
3343 GetMetaData(&smi, msgnum);
3344 smi.meta_refcount += incr;
3346 end_critical_section(S_SUPPMSGMAIN);
3347 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3349 // If the reference count is now zero, delete both the message and its metadata record.
3350 if (smi.meta_refcount == 0) {
3351 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3353 // Call delete hooks with NULL room to show it has gone altogether
3354 PerformDeleteHooks(NULL, msgnum);
3356 // Remove from message base
3358 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3359 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
3361 // Remove metadata record
3362 delnum = (0L - msgnum);
3363 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3368 // Write a generic object to this room
3369 // Returns the message number of the written object, in case you need it.
3370 long CtdlWriteObject(char *req_room, // Room to stuff it in
3371 char *content_type, // MIME type of this object
3372 char *raw_message, // Data to be written
3373 off_t raw_length, // Size of raw_message
3374 struct ctdluser *is_mailbox, // Mailbox room?
3375 int is_binary, // Is encoding necessary?
3376 unsigned int flags // Internal save flags
3378 struct ctdlroom qrbuf;
3379 char roomname[ROOMNAMELEN];
3380 struct CtdlMessage *msg;
3381 StrBuf *encoded_message = NULL;
3383 if (is_mailbox != NULL) {
3384 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3387 safestrncpy(roomname, req_room, sizeof(roomname));
3390 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3393 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3396 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3399 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3400 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3401 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3404 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3407 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3411 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3414 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3417 syslog(LOG_DEBUG, "msgbase: allocating");
3418 msg = malloc(sizeof(struct CtdlMessage));
3419 memset(msg, 0, sizeof(struct CtdlMessage));
3420 msg->cm_magic = CTDLMESSAGE_MAGIC;
3421 msg->cm_anon_type = MES_NORMAL;
3422 msg->cm_format_type = 4;
3423 CM_SetField(msg, eAuthor, CC->user.fullname);
3424 CM_SetField(msg, eOriginalRoom, req_room);
3425 msg->cm_flags = flags;
3427 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3429 /* Create the requested room if we have to. */
3430 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3431 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3434 /* Now write the data */
3435 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3441 /************************************************************************/
3442 /* MODULE INITIALIZATION */
3443 /************************************************************************/
3445 char *ctdl_module_init_msgbase(void) {
3447 FillMsgKeyLookupTable();
3450 /* return our module id for the log */