2 * Implements the message store.
4 * Copyright (c) 1987-2021 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #include <libcitadel.h>
22 #include "ctdl_module.h"
23 #include "citserver.h"
26 #include "clientsocket.h"
30 #include "internet_addressing.h"
31 #include "euidindex.h"
33 #include "journaling.h"
35 struct addresses_to_be_filed *atbf = NULL;
38 * These are the four-character field headers we use when outputting
39 * messages in Citadel format (as opposed to RFC822 format).
42 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
43 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
44 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
45 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
46 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
47 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
48 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
49 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
51 "from", // A -> eAuthor
52 NULL, // B -> eBig_message
53 NULL, // C (formerly used as eRemoteRoom)
54 NULL, // D (formerly used as eDestination)
55 "exti", // E -> eXclusivID
56 "rfca", // F -> erFc822Addr
58 "hnod", // H (formerly used as eHumanNode)
59 "msgn", // I -> emessageId
60 "jrnl", // J -> eJournal
61 "rep2", // K -> eReplyTo
62 "list", // L -> eListID
63 "text", // M -> eMesageText
64 NULL, // N (formerly used as eNodename)
65 "room", // O -> eOriginalRoom
66 "path", // P -> eMessagePath
68 "rcpt", // R -> eRecipient
69 NULL, // S (formerly used as eSpecialField)
70 "time", // T -> eTimestamp
71 "subj", // U -> eMsgSubject
72 "nvto", // V -> eenVelopeTo
73 "wefw", // W -> eWeferences
75 "cccc", // Y -> eCarbonCopY
80 HashList *msgKeyLookup = NULL;
82 int GetFieldFromMnemonic(eMsgField *f, const char* c)
85 if (GetHash(msgKeyLookup, c, 4, &v)) {
92 void FillMsgKeyLookupTable(void)
96 msgKeyLookup = NewHash (1, FourHash);
98 for (i=0; i < 91; i++) {
99 if (msgkeys[i] != NULL) {
100 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
106 eMsgField FieldOrder[] = {
107 /* Important fields */
115 /* Semi-important fields */
120 /* G is not used yet */
123 /* Q is not used yet */
125 /* X is not used yet */
126 /* Z is not used yet */
133 /* Message text (MUST be last) */
135 /* Not saved to disk:
140 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
143 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
145 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
149 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
151 if (Msg->cm_fields[which] != NULL) {
152 free (Msg->cm_fields[which]);
154 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
155 length = strlen(buf);
157 Msg->cm_fields[which] = malloc(length + 1);
158 memcpy(Msg->cm_fields[which], buf, length);
159 Msg->cm_fields[which][length] = '\0';
160 Msg->cm_lengths[which] = length;
164 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
168 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
169 CM_SetField(Msg, which, buf, len);
173 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
175 if (Msg->cm_fields[WhichToCut] == NULL)
178 if (Msg->cm_lengths[WhichToCut] > maxlen)
180 Msg->cm_fields[WhichToCut][maxlen] = '\0';
181 Msg->cm_lengths[WhichToCut] = maxlen;
186 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
188 if (Msg->cm_fields[which] != NULL)
189 free (Msg->cm_fields[which]);
190 Msg->cm_fields[which] = NULL;
191 Msg->cm_lengths[which] = 0;
195 void CM_Flush(struct CtdlMessage *Msg)
199 if (CM_IsValidMsg(Msg) == 0) {
203 for (i = 0; i < 256; ++i) {
204 CM_FlushField(Msg, i);
209 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
212 if (Msg->cm_fields[WhichToPutTo] != NULL) {
213 free (Msg->cm_fields[WhichToPutTo]);
216 if (Msg->cm_fields[WhichtToCopy] != NULL) {
217 len = Msg->cm_lengths[WhichtToCopy];
218 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
219 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
220 Msg->cm_fields[WhichToPutTo][len] = '\0';
221 Msg->cm_lengths[WhichToPutTo] = len;
224 Msg->cm_fields[WhichToPutTo] = NULL;
225 Msg->cm_lengths[WhichToPutTo] = 0;
230 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
232 if (Msg->cm_fields[which] != NULL) {
237 oldmsgsize = Msg->cm_lengths[which] + 1;
238 newmsgsize = length + oldmsgsize;
240 new = malloc(newmsgsize);
241 memcpy(new, buf, length);
242 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
243 free(Msg->cm_fields[which]);
244 Msg->cm_fields[which] = new;
245 Msg->cm_lengths[which] = newmsgsize - 1;
248 Msg->cm_fields[which] = malloc(length + 1);
249 memcpy(Msg->cm_fields[which], buf, length);
250 Msg->cm_fields[which][length] = '\0';
251 Msg->cm_lengths[which] = length;
256 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
258 if (Msg->cm_fields[which] != NULL) {
259 free (Msg->cm_fields[which]);
262 Msg->cm_fields[which] = *buf;
264 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
265 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
268 Msg->cm_lengths[which] = length;
273 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
275 if (Msg->cm_fields[which] != NULL) {
276 free (Msg->cm_fields[which]);
279 Msg->cm_lengths[which] = StrLength(*buf);
280 Msg->cm_fields[which] = SmashStrBuf(buf);
284 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
286 if (Msg->cm_fields[which] != NULL) {
287 *retlen = Msg->cm_lengths[which];
288 *ret = Msg->cm_fields[which];
289 Msg->cm_fields[which] = NULL;
290 Msg->cm_lengths[which] = 0;
300 * Returns 1 if the supplied pointer points to a valid Citadel message.
301 * If the pointer is NULL or the magic number check fails, returns 0.
303 int CM_IsValidMsg(struct CtdlMessage *msg) {
307 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
308 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
315 void CM_FreeContents(struct CtdlMessage *msg)
319 for (i = 0; i < 256; ++i)
320 if (msg->cm_fields[i] != NULL) {
321 free(msg->cm_fields[i]);
322 msg->cm_lengths[i] = 0;
325 msg->cm_magic = 0; /* just in case */
330 * 'Destructor' for struct CtdlMessage
332 void CM_Free(struct CtdlMessage *msg)
334 if (CM_IsValidMsg(msg) == 0) {
335 if (msg != NULL) free (msg);
338 CM_FreeContents(msg);
343 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
346 len = OrgMsg->cm_lengths[i];
347 NewMsg->cm_fields[i] = malloc(len + 1);
348 if (NewMsg->cm_fields[i] == NULL) {
351 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
352 NewMsg->cm_fields[i][len] = '\0';
353 NewMsg->cm_lengths[i] = len;
358 struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
361 struct CtdlMessage *NewMsg;
363 if (CM_IsValidMsg(OrgMsg) == 0) {
366 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
367 if (NewMsg == NULL) {
371 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
373 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
375 for (i = 0; i < 256; ++i) {
376 if (OrgMsg->cm_fields[i] != NULL) {
377 if (!CM_DupField(i, OrgMsg, NewMsg)) {
388 /* Determine if a given message matches the fields in a message template.
389 * Return 0 for a successful match.
391 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
394 /* If there aren't any fields in the template, all messages will
397 if (template == NULL) return(0);
399 /* Null messages are bogus. */
400 if (msg == NULL) return(1);
402 for (i='A'; i<='Z'; ++i) {
403 if (template->cm_fields[i] != NULL) {
404 if (msg->cm_fields[i] == NULL) {
405 /* Considered equal if temmplate is empty string */
406 if (IsEmptyStr(template->cm_fields[i])) continue;
409 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
410 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
415 /* All compares succeeded: we have a match! */
421 * Retrieve the "seen" message list for the current room.
423 void CtdlGetSeen(char *buf, int which_set) {
426 /* Learn about the user and room in question */
427 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
429 if (which_set == ctdlsetseen_seen) {
430 safestrncpy(buf, vbuf.v_seen, SIZ);
432 if (which_set == ctdlsetseen_answered) {
433 safestrncpy(buf, vbuf.v_answered, SIZ);
439 * Manipulate the "seen msgs" string (or other message set strings)
441 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
442 int target_setting, int which_set,
443 struct ctdluser *which_user, struct ctdlroom *which_room) {
444 struct cdbdata *cdbfr;
458 char *is_set; /* actually an array of booleans */
460 /* Don't bother doing *anything* if we were passed a list of zero messages */
461 if (num_target_msgnums < 1) {
465 /* If no room was specified, we go with the current room. */
467 which_room = &CC->room;
470 /* If no user was specified, we go with the current user. */
472 which_user = &CC->user;
475 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
476 num_target_msgnums, target_msgnums[0],
477 (target_setting ? "SET" : "CLEAR"),
481 /* Learn about the user and room in question */
482 CtdlGetRelationship(&vbuf, which_user, which_room);
484 /* Load the message list */
485 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
487 msglist = (long *) cdbfr->ptr;
488 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
489 num_msgs = cdbfr->len / sizeof(long);
492 return; /* No messages at all? No further action. */
495 is_set = malloc(num_msgs * sizeof(char));
496 memset(is_set, 0, (num_msgs * sizeof(char)) );
498 /* Decide which message set we're manipulating */
500 case ctdlsetseen_seen:
501 vset = NewStrBufPlain(vbuf.v_seen, -1);
503 case ctdlsetseen_answered:
504 vset = NewStrBufPlain(vbuf.v_answered, -1);
511 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
512 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
513 for (i=0; i<num_msgs; ++i) {
514 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
516 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
517 for (k=0; k<num_target_msgnums; ++k) {
518 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
522 /* Translate the existing sequence set into an array of booleans */
523 setstr = NewStrBuf();
527 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
529 StrBufExtract_token(lostr, setstr, 0, ':');
530 if (StrBufNum_tokens(setstr, ':') >= 2) {
531 StrBufExtract_token(histr, setstr, 1, ':');
535 StrBufAppendBuf(histr, lostr, 0);
538 if (!strcmp(ChrPtr(histr), "*")) {
545 for (i = 0; i < num_msgs; ++i) {
546 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
555 /* Now translate the array of booleans back into a sequence set */
561 for (i=0; i<num_msgs; ++i) {
565 for (k=0; k<num_target_msgnums; ++k) {
566 if (msglist[i] == target_msgnums[k]) {
567 is_seen = target_setting;
571 if ((was_seen == 0) && (is_seen == 1)) {
574 else if ((was_seen == 1) && (is_seen == 0)) {
577 if (StrLength(vset) > 0) {
578 StrBufAppendBufPlain(vset, HKEY(","), 0);
581 StrBufAppendPrintf(vset, "%ld", hi);
584 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
588 if ((is_seen) && (i == num_msgs - 1)) {
589 if (StrLength(vset) > 0) {
590 StrBufAppendBufPlain(vset, HKEY(","), 0);
592 if ((i==0) || (was_seen == 0)) {
593 StrBufAppendPrintf(vset, "%ld", msglist[i]);
596 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
604 * We will have to stuff this string back into a 4096 byte buffer, so if it's
605 * larger than that now, truncate it by removing tokens from the beginning.
606 * The limit of 100 iterations is there to prevent an infinite loop in case
607 * something unexpected happens.
609 int number_of_truncations = 0;
610 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
611 StrBufRemove_token(vset, 0, ',');
612 ++number_of_truncations;
616 * If we're truncating the sequence set of messages marked with the 'seen' flag,
617 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
618 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
620 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
622 first_tok = NewStrBuf();
623 StrBufExtract_token(first_tok, vset, 0, ',');
624 StrBufRemove_token(vset, 0, ',');
626 if (StrBufNum_tokens(first_tok, ':') > 1) {
627 StrBufRemove_token(first_tok, 0, ':');
631 new_set = NewStrBuf();
632 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
633 StrBufAppendBuf(new_set, first_tok, 0);
634 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
635 StrBufAppendBuf(new_set, vset, 0);
638 FreeStrBuf(&first_tok);
642 /* Decide which message set we're manipulating */
644 case ctdlsetseen_seen:
645 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
647 case ctdlsetseen_answered:
648 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
654 CtdlSetRelationship(&vbuf, which_user, which_room);
660 * API function to perform an operation for each qualifying message in the
661 * current room. (Returns the number of messages processed.)
663 int CtdlForEachMessage(int mode, long ref, char *search_string,
665 struct CtdlMessage *compare,
666 ForEachMsgCallback CallBack,
671 struct cdbdata *cdbfr;
672 long *msglist = NULL;
674 int num_processed = 0;
677 struct CtdlMessage *msg = NULL;
680 int printed_lastold = 0;
681 int num_search_msgs = 0;
682 long *search_msgs = NULL;
684 int need_to_free_re = 0;
687 if ((content_type) && (!IsEmptyStr(content_type))) {
688 regcomp(&re, content_type, 0);
692 /* Learn about the user and room in question */
693 if (server_shutting_down) {
694 if (need_to_free_re) regfree(&re);
697 CtdlGetUser(&CC->user, CC->curr_user);
699 if (server_shutting_down) {
700 if (need_to_free_re) regfree(&re);
703 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
705 if (server_shutting_down) {
706 if (need_to_free_re) regfree(&re);
710 /* Load the message list */
711 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
713 if (need_to_free_re) regfree(&re);
714 return 0; /* No messages at all? No further action. */
717 msglist = (long *) cdbfr->ptr;
718 num_msgs = cdbfr->len / sizeof(long);
720 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
721 cdb_free(cdbfr); /* we own this memory now */
724 * Now begin the traversal.
726 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
728 /* If the caller is looking for a specific MIME type, filter
729 * out all messages which are not of the type requested.
731 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
733 /* This call to GetMetaData() sits inside this loop
734 * so that we only do the extra database read per msg
735 * if we need to. Doing the extra read all the time
736 * really kills the server. If we ever need to use
737 * metadata for another search criterion, we need to
738 * move the read somewhere else -- but still be smart
739 * enough to only do the read if the caller has
740 * specified something that will need it.
742 if (server_shutting_down) {
743 if (need_to_free_re) regfree(&re);
747 GetMetaData(&smi, msglist[a]);
749 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
750 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
756 num_msgs = sort_msglist(msglist, num_msgs);
758 /* If a template was supplied, filter out the messages which
759 * don't match. (This could induce some delays!)
762 if (compare != NULL) {
763 for (a = 0; a < num_msgs; ++a) {
764 if (server_shutting_down) {
765 if (need_to_free_re) regfree(&re);
769 msg = CtdlFetchMessage(msglist[a], 1);
771 if (CtdlMsgCmp(msg, compare)) {
780 /* If a search string was specified, get a message list from
781 * the full text index and remove messages which aren't on both
785 * Since the lists are sorted and strictly ascending, and the
786 * output list is guaranteed to be shorter than or equal to the
787 * input list, we overwrite the bottom of the input list. This
788 * eliminates the need to memmove big chunks of the list over and
791 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
793 /* Call search module via hook mechanism.
794 * NULL means use any search function available.
795 * otherwise replace with a char * to name of search routine
797 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
799 if (num_search_msgs > 0) {
803 orig_num_msgs = num_msgs;
805 for (i=0; i<orig_num_msgs; ++i) {
806 for (j=0; j<num_search_msgs; ++j) {
807 if (msglist[i] == search_msgs[j]) {
808 msglist[num_msgs++] = msglist[i];
814 num_msgs = 0; /* No messages qualify */
816 if (search_msgs != NULL) free(search_msgs);
818 /* Now that we've purged messages which don't contain the search
819 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
826 * Now iterate through the message list, according to the
827 * criteria supplied by the caller.
830 for (a = 0; a < num_msgs; ++a) {
831 if (server_shutting_down) {
832 if (need_to_free_re) regfree(&re);
834 return num_processed;
836 thismsg = msglist[a];
837 if (mode == MSGS_ALL) {
841 is_seen = is_msg_in_sequence_set(
842 vbuf.v_seen, thismsg);
843 if (is_seen) lastold = thismsg;
849 || ((mode == MSGS_OLD) && (is_seen))
850 || ((mode == MSGS_NEW) && (!is_seen))
851 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
852 || ((mode == MSGS_FIRST) && (a < ref))
853 || ((mode == MSGS_GT) && (thismsg > ref))
854 || ((mode == MSGS_LT) && (thismsg < ref))
855 || ((mode == MSGS_EQ) && (thismsg == ref))
858 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
860 CallBack(lastold, userdata);
866 CallBack(thismsg, userdata);
871 if (need_to_free_re) regfree(&re);
874 * We cache the most recent msglist in order to do security checks later
876 if (CC->client_socket > 0) {
877 if (CC->cached_msglist != NULL) {
878 free(CC->cached_msglist);
880 CC->cached_msglist = msglist;
881 CC->cached_num_msgs = num_msgs;
887 return num_processed;
892 * memfmout() - Citadel text formatter and paginator.
893 * Although the original purpose of this routine was to format
894 * text to the reader's screen width, all we're really using it
895 * for here is to format text out to 80 columns before sending it
896 * to the client. The client software may reformat it again.
899 char *mptr, /* where are we going to get our text from? */
900 const char *nl /* string to terminate lines with */
903 unsigned char ch = 0;
910 while (ch=*(mptr++), ch != 0) {
913 if (client_write(outbuf, len) == -1) {
914 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
918 if (client_write(nl, nllen) == -1) {
919 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
924 else if (ch == '\r') {
925 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
927 else if (isspace(ch)) {
928 if (column > 72) { /* Beyond 72 columns, break on the next space */
929 if (client_write(outbuf, len) == -1) {
930 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
934 if (client_write(nl, nllen) == -1) {
935 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
948 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
949 if (client_write(outbuf, len) == -1) {
950 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
954 if (client_write(nl, nllen) == -1) {
955 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
963 if (client_write(outbuf, len) == -1) {
964 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
967 client_write(nl, nllen);
974 * Callback function for mime parser that simply lists the part
976 void list_this_part(char *name, char *filename, char *partnum, char *disp,
977 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
978 char *cbid, void *cbuserdata)
982 ma = (struct ma_info *)cbuserdata;
983 if (ma->is_ma == 0) {
984 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
998 * Callback function for multipart prefix
1000 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1002 char *cbid, void *cbuserdata)
1006 ma = (struct ma_info *)cbuserdata;
1007 if (!strcasecmp(cbtype, "multipart/alternative")) {
1011 if (ma->is_ma == 0) {
1012 cprintf("pref=%s|%s\n", partnum, cbtype);
1018 * Callback function for multipart sufffix
1020 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1021 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1022 char *cbid, void *cbuserdata)
1026 ma = (struct ma_info *)cbuserdata;
1027 if (ma->is_ma == 0) {
1028 cprintf("suff=%s|%s\n", partnum, cbtype);
1030 if (!strcasecmp(cbtype, "multipart/alternative")) {
1037 * Callback function for mime parser that opens a section for downloading
1038 * we use serv_files function here:
1040 extern void OpenCmdResult(char *filename, const char *mime_type);
1041 void mime_download(char *name, char *filename, char *partnum, char *disp,
1042 void *content, char *cbtype, char *cbcharset, size_t length,
1043 char *encoding, char *cbid, void *cbuserdata)
1047 /* Silently go away if there's already a download open. */
1048 if (CC->download_fp != NULL)
1052 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1053 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1055 CC->download_fp = tmpfile();
1056 if (CC->download_fp == NULL) {
1057 syslog(LOG_EMERG, "msgbase: mime_download() couldn't write: %m");
1058 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
1062 rv = fwrite(content, length, 1, CC->download_fp);
1064 syslog(LOG_EMERG, "msgbase: mime_download() Couldn't write: %m");
1065 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
1066 fclose(CC->download_fp);
1067 CC->download_fp = NULL;
1070 fflush(CC->download_fp);
1071 rewind(CC->download_fp);
1073 OpenCmdResult(filename, cbtype);
1079 * Callback function for mime parser that outputs a section all at once.
1080 * We can specify the desired section by part number *or* content-id.
1082 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1083 void *content, char *cbtype, char *cbcharset, size_t length,
1084 char *encoding, char *cbid, void *cbuserdata)
1086 int *found_it = (int *)cbuserdata;
1089 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1090 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1093 cprintf("%d %d|-1|%s|%s|%s\n",
1100 client_write(content, length);
1105 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1107 struct CtdlMessage *ret = NULL;
1109 const char *upper_bound;
1111 cit_uint8_t field_header;
1115 upper_bound = Buffer + Length;
1120 /* Parse the three bytes that begin EVERY message on disk.
1121 * The first is always 0xFF, the on-disk magic number.
1122 * The second is the anonymous/public type byte.
1123 * The third is the format type byte (vari, fixed, or MIME).
1127 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1130 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1131 memset(ret, 0, sizeof(struct CtdlMessage));
1133 ret->cm_magic = CTDLMESSAGE_MAGIC;
1134 ret->cm_anon_type = *mptr++; /* Anon type byte */
1135 ret->cm_format_type = *mptr++; /* Format type byte */
1138 * The rest is zero or more arbitrary fields. Load them in.
1139 * We're done when we encounter either a zero-length field or
1140 * have just processed the 'M' (message text) field.
1143 field_header = '\0';
1146 /* work around possibly buggy messages: */
1147 while (field_header == '\0') {
1148 if (mptr >= upper_bound) {
1151 field_header = *mptr++;
1153 if (mptr >= upper_bound) {
1156 which = field_header;
1159 CM_SetField(ret, which, mptr, len);
1161 mptr += len + 1; /* advance to next field */
1163 } while ((mptr < upper_bound) && (field_header != 'M'));
1170 * Load a message from disk into memory.
1171 * This is used by CtdlOutputMsg() and other fetch functions.
1173 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1174 * using the CM_Free(); function.
1176 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1178 struct cdbdata *dmsgtext;
1179 struct CtdlMessage *ret = NULL;
1181 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1182 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1183 if (dmsgtext == NULL) {
1184 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Failed!", msgnum, with_body);
1188 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
1189 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1190 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1193 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1201 /* Always make sure there's something in the msg text field. If
1202 * it's NULL, the message text is most likely stored separately,
1203 * so go ahead and fetch that. Failing that, just set a dummy
1204 * body so other code doesn't barf.
1206 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1207 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1208 if (dmsgtext != NULL) {
1209 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1213 if (CM_IsEmpty(ret, eMesageText)) {
1214 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1222 * Pre callback function for multipart/alternative
1224 * NOTE: this differs from the standard behavior for a reason. Normally when
1225 * displaying multipart/alternative you want to show the _last_ usable
1226 * format in the message. Here we show the _first_ one, because it's
1227 * usually text/plain. Since this set of functions is designed for text
1228 * output to non-MIME-aware clients, this is the desired behavior.
1231 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1232 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1233 char *cbid, void *cbuserdata)
1237 ma = (struct ma_info *)cbuserdata;
1238 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1239 if (!strcasecmp(cbtype, "multipart/alternative")) {
1243 if (!strcasecmp(cbtype, "message/rfc822")) {
1250 * Post callback function for multipart/alternative
1252 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1253 void *content, char *cbtype, char *cbcharset, size_t length,
1254 char *encoding, char *cbid, void *cbuserdata)
1258 ma = (struct ma_info *)cbuserdata;
1259 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1260 if (!strcasecmp(cbtype, "multipart/alternative")) {
1264 if (!strcasecmp(cbtype, "message/rfc822")) {
1271 * Inline callback function for mime parser that wants to display text
1273 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1274 void *content, char *cbtype, char *cbcharset, size_t length,
1275 char *encoding, char *cbid, void *cbuserdata)
1282 ma = (struct ma_info *)cbuserdata;
1285 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1286 partnum, filename, cbtype, (long)length
1290 * If we're in the middle of a multipart/alternative scope and
1291 * we've already printed another section, skip this one.
1293 if ( (ma->is_ma) && (ma->did_print) ) {
1294 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1299 if ( (!strcasecmp(cbtype, "text/plain"))
1300 || (IsEmptyStr(cbtype)) ) {
1303 client_write(wptr, length);
1304 if (wptr[length-1] != '\n') {
1311 if (!strcasecmp(cbtype, "text/html")) {
1312 ptr = html_to_ascii(content, length, 80);
1314 client_write(ptr, wlen);
1315 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1322 if (ma->use_fo_hooks) {
1323 if (PerformFixedOutputHooks(cbtype, content, length)) {
1324 /* above function returns nonzero if it handled the part */
1329 if (strncasecmp(cbtype, "multipart/", 10)) {
1330 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1331 partnum, filename, cbtype, (long)length);
1338 * The client is elegant and sophisticated and wants to be choosy about
1339 * MIME content types, so figure out which multipart/alternative part
1340 * we're going to send.
1342 * We use a system of weights. When we find a part that matches one of the
1343 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1344 * and then set ma->chosen_pref to that MIME type's position in our preference
1345 * list. If we then hit another match, we only replace the first match if
1346 * the preference value is lower.
1348 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1349 void *content, char *cbtype, char *cbcharset, size_t length,
1350 char *encoding, char *cbid, void *cbuserdata)
1356 ma = (struct ma_info *)cbuserdata;
1358 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1359 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1360 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1361 if (i < ma->chosen_pref) {
1362 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1363 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1364 ma->chosen_pref = i;
1372 * Now that we've chosen our preferred part, output it.
1374 void output_preferred(char *name,
1388 int add_newline = 0;
1391 char *decoded = NULL;
1392 size_t bytes_decoded;
1395 ma = (struct ma_info *)cbuserdata;
1397 /* This is not the MIME part you're looking for... */
1398 if (strcasecmp(partnum, ma->chosen_part)) return;
1400 /* If the content-type of this part is in our preferred formats
1401 * list, we can simply output it verbatim.
1403 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1404 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1405 if (!strcasecmp(buf, cbtype)) {
1406 /* Yeah! Go! W00t!! */
1407 if (ma->dont_decode == 0)
1408 rc = mime_decode_now (content,
1414 break; /* Give us the chance, maybe theres another one. */
1416 if (rc == 0) text_content = (char *)content;
1418 text_content = decoded;
1419 length = bytes_decoded;
1422 if (text_content[length-1] != '\n') {
1425 cprintf("Content-type: %s", cbtype);
1426 if (!IsEmptyStr(cbcharset)) {
1427 cprintf("; charset=%s", cbcharset);
1429 cprintf("\nContent-length: %d\n",
1430 (int)(length + add_newline) );
1431 if (!IsEmptyStr(encoding)) {
1432 cprintf("Content-transfer-encoding: %s\n", encoding);
1435 cprintf("Content-transfer-encoding: 7bit\n");
1437 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1439 if (client_write(text_content, length) == -1)
1441 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1444 if (add_newline) cprintf("\n");
1445 if (decoded != NULL) free(decoded);
1450 /* No translations required or possible: output as text/plain */
1451 cprintf("Content-type: text/plain\n\n");
1453 if (ma->dont_decode == 0)
1454 rc = mime_decode_now (content,
1460 return; /* Give us the chance, maybe theres another one. */
1462 if (rc == 0) text_content = (char *)content;
1464 text_content = decoded;
1465 length = bytes_decoded;
1468 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1469 length, encoding, cbid, cbuserdata);
1470 if (decoded != NULL) free(decoded);
1475 char desired_section[64];
1482 * Callback function for
1484 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1485 void *content, char *cbtype, char *cbcharset, size_t length,
1486 char *encoding, char *cbid, void *cbuserdata)
1488 struct encapmsg *encap;
1490 encap = (struct encapmsg *)cbuserdata;
1492 /* Only proceed if this is the desired section... */
1493 if (!strcasecmp(encap->desired_section, partnum)) {
1494 encap->msglen = length;
1495 encap->msg = malloc(length + 2);
1496 memcpy(encap->msg, content, length);
1503 * Determine whether the specified message exists in the cached_msglist
1504 * (This is a security check)
1506 int check_cached_msglist(long msgnum) {
1508 /* cases in which we skip the check */
1509 if (!CC) return om_ok; /* not a session */
1510 if (CC->client_socket <= 0) return om_ok; /* not a client session */
1511 if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1512 if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1515 /* Do a binary search within the cached_msglist for the requested msgnum */
1517 int max = (CC->cached_num_msgs - 1);
1519 while (max >= min) {
1520 int middle = min + (max-min) / 2 ;
1521 if (msgnum == CC->cached_msglist[middle]) {
1524 if (msgnum > CC->cached_msglist[middle]) {
1532 return om_access_denied;
1537 * Get a message off disk. (returns om_* values found in msgbase.h)
1540 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1541 int mode, /* how would you like that message? */
1542 int headers_only, /* eschew the message body? */
1543 int do_proto, /* do Citadel protocol responses? */
1544 int crlf, /* Use CRLF newlines instead of LF? */
1545 char *section, /* NULL or a message/rfc822 section */
1546 int flags, /* various flags; see msgbase.h */
1551 struct CtdlMessage *TheMessage = NULL;
1552 int retcode = CIT_OK;
1553 struct encapmsg encap;
1556 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1558 (section ? section : "<>")
1561 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1564 if (r == om_not_logged_in) {
1565 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1568 cprintf("%d An unknown error has occurred.\n", ERROR);
1575 * Check to make sure the message is actually IN this room
1577 r = check_cached_msglist(msg_num);
1578 if (r == om_access_denied) {
1579 /* Not in the cache? We get ONE shot to check it again. */
1580 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1581 r = check_cached_msglist(msg_num);
1584 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1585 msg_num, CC->room.QRname
1588 if (r == om_access_denied) {
1589 cprintf("%d message %ld was not found in this room\n",
1590 ERROR + HIGHER_ACCESS_REQUIRED,
1599 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1600 * request that we don't even bother loading the body into memory.
1602 if (headers_only == HEADERS_FAST) {
1603 TheMessage = CtdlFetchMessage(msg_num, 0);
1606 TheMessage = CtdlFetchMessage(msg_num, 1);
1609 if (TheMessage == NULL) {
1610 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1611 ERROR + MESSAGE_NOT_FOUND, msg_num);
1612 return(om_no_such_msg);
1615 /* Here is the weird form of this command, to process only an
1616 * encapsulated message/rfc822 section.
1618 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1619 memset(&encap, 0, sizeof encap);
1620 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1621 mime_parser(CM_RANGE(TheMessage, eMesageText),
1622 *extract_encapsulated_message,
1623 NULL, NULL, (void *)&encap, 0
1626 if ((Author != NULL) && (*Author == NULL))
1629 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1631 if ((Address != NULL) && (*Address == NULL))
1634 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1636 if ((MessageID != NULL) && (*MessageID == NULL))
1639 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1641 CM_Free(TheMessage);
1645 encap.msg[encap.msglen] = 0;
1646 TheMessage = convert_internet_message(encap.msg);
1647 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1649 /* Now we let it fall through to the bottom of this
1650 * function, because TheMessage now contains the
1651 * encapsulated message instead of the top-level
1652 * message. Isn't that neat?
1657 cprintf("%d msg %ld has no part %s\n",
1658 ERROR + MESSAGE_NOT_FOUND,
1662 retcode = om_no_such_msg;
1667 /* Ok, output the message now */
1668 if (retcode == CIT_OK)
1669 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1670 if ((Author != NULL) && (*Author == NULL))
1673 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1675 if ((Address != NULL) && (*Address == NULL))
1678 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1680 if ((MessageID != NULL) && (*MessageID == NULL))
1683 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1686 CM_Free(TheMessage);
1692 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1695 char display_name[256];
1697 /* begin header processing loop for Citadel message format */
1698 safestrncpy(display_name, "<unknown>", sizeof display_name);
1699 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1700 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1701 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1702 safestrncpy(display_name, "****", sizeof display_name);
1704 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1705 safestrncpy(display_name, "anonymous", sizeof display_name);
1708 safestrncpy(display_name, buf, sizeof display_name);
1710 if ((is_room_aide())
1711 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1712 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1713 size_t tmp = strlen(display_name);
1714 snprintf(&display_name[tmp],
1715 sizeof display_name - tmp,
1720 /* Now spew the header fields in the order we like them. */
1721 for (i=0; i< NDiskFields; ++i) {
1723 Field = FieldOrder[i];
1724 if (Field != eMesageText) {
1725 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1726 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1727 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1729 if (Field == eAuthor) {
1731 cprintf("%s=%s\n", msgkeys[Field], display_name);
1734 /* Masquerade display name if needed */
1737 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1740 /* Give the client a hint about whether the message originated locally */
1741 if (Field == erFc822Addr) {
1742 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1743 cprintf("locl=yes\n"); // message originated locally.
1755 void OutputRFC822MsgHeaders(
1756 struct CtdlMessage *TheMessage,
1757 int flags, /* should the message be exported clean */
1758 const char *nl, int nlen,
1759 char *mid, long sizeof_mid,
1760 char *suser, long sizeof_suser,
1761 char *luser, long sizeof_luser,
1762 char *fuser, long sizeof_fuser,
1763 char *snode, long sizeof_snode)
1765 char datestamp[100];
1766 int subject_found = 0;
1773 for (i = 0; i < NDiskFields; ++i) {
1774 if (TheMessage->cm_fields[FieldOrder[i]]) {
1775 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1776 switch (FieldOrder[i]) {
1778 safestrncpy(luser, mptr, sizeof_luser);
1779 safestrncpy(suser, mptr, sizeof_suser);
1782 if ((flags & QP_EADDR) != 0) {
1783 mptr = qp_encode_email_addrs(mptr);
1785 sanitize_truncated_recipient(mptr);
1786 cprintf("CC: %s%s", mptr, nl);
1789 cprintf("Return-Path: %s%s", mptr, nl);
1792 cprintf("List-ID: %s%s", mptr, nl);
1795 if ((flags & QP_EADDR) != 0)
1796 mptr = qp_encode_email_addrs(mptr);
1798 while ((*hptr != '\0') && isspace(*hptr))
1800 if (!IsEmptyStr(hptr))
1801 cprintf("Envelope-To: %s%s", hptr, nl);
1804 cprintf("Subject: %s%s", mptr, nl);
1808 safestrncpy(mid, mptr, sizeof_mid);
1811 safestrncpy(fuser, mptr, sizeof_fuser);
1813 if (haschar(mptr, '@') == 0) {
1814 sanitize_truncated_recipient(mptr);
1815 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1819 if ((flags & QP_EADDR) != 0) {
1820 mptr = qp_encode_email_addrs(mptr);
1822 sanitize_truncated_recipient(mptr);
1823 cprintf("To: %s", mptr);
1828 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1829 cprintf("Date: %s%s", datestamp, nl);
1832 cprintf("References: ");
1833 k = num_tokens(mptr, '|');
1834 for (j=0; j<k; ++j) {
1835 extract_token(buf, mptr, j, '|', sizeof buf);
1836 cprintf("<%s>", buf);
1847 while ((*hptr != '\0') && isspace(*hptr))
1849 if (!IsEmptyStr(hptr))
1850 cprintf("Reply-To: %s%s", mptr, nl);
1862 /* these don't map to mime message headers. */
1865 if (mptr != mpptr) {
1870 if (subject_found == 0) {
1871 cprintf("Subject: (no subject)%s", nl);
1876 void Dump_RFC822HeadersBody(
1877 struct CtdlMessage *TheMessage,
1878 int headers_only, /* eschew the message body? */
1879 int flags, /* should the bessage be exported clean? */
1880 const char *nl, int nlen)
1882 cit_uint8_t prev_ch;
1884 const char *StartOfText = StrBufNOTNULL;
1887 int nllen = strlen(nl);
1891 mptr = TheMessage->cm_fields[eMesageText];
1894 while (*mptr != '\0') {
1895 if (*mptr == '\r') {
1902 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1904 eoh = *(mptr+1) == '\n';
1908 StartOfText = strchr(StartOfText, '\n');
1909 StartOfText = strchr(StartOfText, '\n');
1912 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1913 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1914 ((headers_only != HEADERS_NONE) &&
1915 (headers_only != HEADERS_ONLY))
1917 if (*mptr == '\n') {
1918 memcpy(&outbuf[outlen], nl, nllen);
1920 outbuf[outlen] = '\0';
1923 outbuf[outlen++] = *mptr;
1927 if (flags & ESC_DOT) {
1928 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1929 outbuf[outlen++] = '.';
1934 if (outlen > 1000) {
1935 if (client_write(outbuf, outlen) == -1) {
1936 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1939 lfSent = (outbuf[outlen - 1] == '\n');
1944 client_write(outbuf, outlen);
1945 lfSent = (outbuf[outlen - 1] == '\n');
1948 client_write(nl, nlen);
1952 /* If the format type on disk is 1 (fixed-format), then we want
1953 * everything to be output completely literally ... regardless of
1954 * what message transfer format is in use.
1956 void DumpFormatFixed(
1957 struct CtdlMessage *TheMessage,
1958 int mode, /* how would you like that message? */
1959 const char *nl, int nllen)
1967 mptr = TheMessage->cm_fields[eMesageText];
1969 if (mode == MT_MIME) {
1970 cprintf("Content-type: text/plain\n\n");
1974 while (ch = *mptr++, ch > 0) {
1978 if ((buflen > 250) && (!xlline)){
1982 while ((buflen > 0) &&
1983 (!isspace(buf[buflen])))
1989 mptr -= tbuflen - buflen;
1995 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1996 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
2001 memcpy (&buf[buflen], nl, nllen);
2005 if (client_write(buf, buflen) == -1) {
2006 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
2018 if (!IsEmptyStr(buf)) {
2019 cprintf("%s%s", buf, nl);
2025 * Get a message off disk. (returns om_* values found in msgbase.h)
2027 int CtdlOutputPreLoadedMsg(
2028 struct CtdlMessage *TheMessage,
2029 int mode, /* how would you like that message? */
2030 int headers_only, /* eschew the message body? */
2031 int do_proto, /* do Citadel protocol responses? */
2032 int crlf, /* Use CRLF newlines instead of LF? */
2033 int flags /* should the bessage be exported clean? */
2036 const char *nl; /* newline string */
2040 /* Buffers needed for RFC822 translation. These are all filled
2041 * using functions that are bounds-checked, and therefore we can
2042 * make them substantially smaller than SIZ.
2050 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
2051 ((TheMessage == NULL) ? "NULL" : "not null"),
2052 mode, headers_only, do_proto, crlf
2055 strcpy(mid, "unknown");
2056 nl = (crlf ? "\r\n" : "\n");
2057 nlen = crlf ? 2 : 1;
2059 if (!CM_IsValidMsg(TheMessage)) {
2060 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2061 return(om_no_such_msg);
2064 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2065 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2067 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2068 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2071 /* Are we downloading a MIME component? */
2072 if (mode == MT_DOWNLOAD) {
2073 if (TheMessage->cm_format_type != FMT_RFC822) {
2075 cprintf("%d This is not a MIME message.\n",
2076 ERROR + ILLEGAL_VALUE);
2077 } else if (CC->download_fp != NULL) {
2078 if (do_proto) cprintf(
2079 "%d You already have a download open.\n",
2080 ERROR + RESOURCE_BUSY);
2082 /* Parse the message text component */
2083 mime_parser(CM_RANGE(TheMessage, eMesageText),
2084 *mime_download, NULL, NULL, NULL, 0);
2085 /* If there's no file open by this time, the requested
2086 * section wasn't found, so print an error
2088 if (CC->download_fp == NULL) {
2089 if (do_proto) cprintf(
2090 "%d Section %s not found.\n",
2091 ERROR + FILE_NOT_FOUND,
2092 CC->download_desired_section);
2095 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2098 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2099 * in a single server operation instead of opening a download file.
2101 if (mode == MT_SPEW_SECTION) {
2102 if (TheMessage->cm_format_type != FMT_RFC822) {
2104 cprintf("%d This is not a MIME message.\n",
2105 ERROR + ILLEGAL_VALUE);
2107 /* Parse the message text component */
2110 mime_parser(CM_RANGE(TheMessage, eMesageText),
2111 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2112 /* If section wasn't found, print an error
2115 if (do_proto) cprintf(
2116 "%d Section %s not found.\n",
2117 ERROR + FILE_NOT_FOUND,
2118 CC->download_desired_section);
2121 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2124 /* now for the user-mode message reading loops */
2125 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2127 /* Does the caller want to skip the headers? */
2128 if (headers_only == HEADERS_NONE) goto START_TEXT;
2130 /* Tell the client which format type we're using. */
2131 if ( (mode == MT_CITADEL) && (do_proto) ) {
2132 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2135 /* nhdr=yes means that we're only displaying headers, no body */
2136 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2137 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2140 cprintf("nhdr=yes\n");
2143 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2144 OutputCtdlMsgHeaders(TheMessage, do_proto);
2147 /* begin header processing loop for RFC822 transfer format */
2152 if (mode == MT_RFC822)
2153 OutputRFC822MsgHeaders(
2158 suser, sizeof(suser),
2159 luser, sizeof(luser),
2160 fuser, sizeof(fuser),
2161 snode, sizeof(snode)
2165 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2166 suser[i] = tolower(suser[i]);
2167 if (!isalnum(suser[i])) suser[i]='_';
2170 if (mode == MT_RFC822) {
2171 /* Construct a fun message id */
2172 cprintf("Message-ID: <%s", mid);
2173 if (strchr(mid, '@')==NULL) {
2174 cprintf("@%s", snode);
2178 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2179 cprintf("From: \"----\" <x@x.org>%s", nl);
2181 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2182 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2184 else if (!IsEmptyStr(fuser)) {
2185 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2188 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2191 /* Blank line signifying RFC822 end-of-headers */
2192 if (TheMessage->cm_format_type != FMT_RFC822) {
2197 /* end header processing loop ... at this point, we're in the text */
2199 if (headers_only == HEADERS_FAST) goto DONE;
2201 /* Tell the client about the MIME parts in this message */
2202 if (TheMessage->cm_format_type == FMT_RFC822) {
2203 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2204 memset(&ma, 0, sizeof(struct ma_info));
2205 mime_parser(CM_RANGE(TheMessage, eMesageText),
2206 (do_proto ? *list_this_part : NULL),
2207 (do_proto ? *list_this_pref : NULL),
2208 (do_proto ? *list_this_suff : NULL),
2211 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2212 Dump_RFC822HeadersBody(
2221 if (headers_only == HEADERS_ONLY) {
2225 /* signify start of msg text */
2226 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2227 if (do_proto) cprintf("text\n");
2230 if (TheMessage->cm_format_type == FMT_FIXED)
2233 mode, /* how would you like that message? */
2236 /* If the message on disk is format 0 (Citadel vari-format), we
2237 * output using the formatter at 80 columns. This is the final output
2238 * form if the transfer format is RFC822, but if the transfer format
2239 * is Citadel proprietary, it'll still work, because the indentation
2240 * for new paragraphs is correct and the client will reformat the
2241 * message to the reader's screen width.
2243 if (TheMessage->cm_format_type == FMT_CITADEL) {
2244 if (mode == MT_MIME) {
2245 cprintf("Content-type: text/x-citadel-variformat\n\n");
2247 memfmout(TheMessage->cm_fields[eMesageText], nl);
2250 /* If the message on disk is format 4 (MIME), we've gotta hand it
2251 * off to the MIME parser. The client has already been told that
2252 * this message is format 1 (fixed format), so the callback function
2253 * we use will display those parts as-is.
2255 if (TheMessage->cm_format_type == FMT_RFC822) {
2256 memset(&ma, 0, sizeof(struct ma_info));
2258 if (mode == MT_MIME) {
2259 ma.use_fo_hooks = 0;
2260 strcpy(ma.chosen_part, "1");
2261 ma.chosen_pref = 9999;
2262 ma.dont_decode = CC->msg4_dont_decode;
2263 mime_parser(CM_RANGE(TheMessage, eMesageText),
2264 *choose_preferred, *fixed_output_pre,
2265 *fixed_output_post, (void *)&ma, 1);
2266 mime_parser(CM_RANGE(TheMessage, eMesageText),
2267 *output_preferred, NULL, NULL, (void *)&ma, 1);
2270 ma.use_fo_hooks = 1;
2271 mime_parser(CM_RANGE(TheMessage, eMesageText),
2272 *fixed_output, *fixed_output_pre,
2273 *fixed_output_post, (void *)&ma, 0);
2278 DONE: /* now we're done */
2279 if (do_proto) cprintf("000\n");
2284 * Save one or more message pointers into a specified room
2285 * (Returns 0 for success, nonzero for failure)
2286 * roomname may be NULL to use the current room
2288 * Note that the 'supplied_msg' field may be set to NULL, in which case
2289 * the message will be fetched from disk, by number, if we need to perform
2290 * replication checks. This adds an additional database read, so if the
2291 * caller already has the message in memory then it should be supplied. (Obviously
2292 * this mode of operation only works if we're saving a single message.)
2294 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2295 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2298 char hold_rm[ROOMNAMELEN];
2299 struct cdbdata *cdbfr;
2302 long highest_msg = 0L;
2305 struct CtdlMessage *msg = NULL;
2307 long *msgs_to_be_merged = NULL;
2308 int num_msgs_to_be_merged = 0;
2311 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2312 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2315 strcpy(hold_rm, CC->room.QRname);
2318 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2319 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2320 if (num_newmsgs > 1) supplied_msg = NULL;
2322 /* Now the regular stuff */
2323 if (CtdlGetRoomLock(&CC->room,
2324 ((roomname != NULL) ? roomname : CC->room.QRname) )
2326 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2327 return(ERROR + ROOM_NOT_FOUND);
2331 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2332 num_msgs_to_be_merged = 0;
2335 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2336 if (cdbfr == NULL) {
2340 msglist = (long *) cdbfr->ptr;
2341 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2342 num_msgs = cdbfr->len / sizeof(long);
2347 /* Create a list of msgid's which were supplied by the caller, but do
2348 * not already exist in the target room. It is absolutely taboo to
2349 * have more than one reference to the same message in a room.
2351 for (i=0; i<num_newmsgs; ++i) {
2353 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2354 if (msglist[j] == newmsgidlist[i]) {
2359 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2363 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2366 * Now merge the new messages
2368 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2369 if (msglist == NULL) {
2370 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2371 free(msgs_to_be_merged);
2372 return (ERROR + INTERNAL_ERROR);
2374 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2375 num_msgs += num_msgs_to_be_merged;
2377 /* Sort the message list, so all the msgid's are in order */
2378 num_msgs = sort_msglist(msglist, num_msgs);
2380 /* Determine the highest message number */
2381 highest_msg = msglist[num_msgs - 1];
2383 /* Write it back to disk. */
2384 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2385 msglist, (int)(num_msgs * sizeof(long)));
2387 /* Free up the memory we used. */
2390 /* Update the highest-message pointer and unlock the room. */
2391 CC->room.QRhighest = highest_msg;
2392 CtdlPutRoomLock(&CC->room);
2394 /* Perform replication checks if necessary */
2395 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2396 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2398 for (i=0; i<num_msgs_to_be_merged; ++i) {
2399 msgid = msgs_to_be_merged[i];
2401 if (supplied_msg != NULL) {
2405 msg = CtdlFetchMessage(msgid, 0);
2409 ReplicationChecks(msg);
2411 /* If the message has an Exclusive ID, index that... */
2412 if (!CM_IsEmpty(msg, eExclusiveID)) {
2413 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2416 /* Free up the memory we may have allocated */
2417 if (msg != supplied_msg) {
2426 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2429 /* Submit this room for processing by hooks */
2430 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2431 if (total_roomhook_errors) {
2432 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2435 /* Go back to the room we were in before we wandered here... */
2436 CtdlGetRoom(&CC->room, hold_rm);
2438 /* Bump the reference count for all messages which were merged */
2439 if (!suppress_refcount_adj) {
2440 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2443 /* Free up memory... */
2444 if (msgs_to_be_merged != NULL) {
2445 free(msgs_to_be_merged);
2448 /* Return success. */
2454 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2457 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2458 int do_repl_check, struct CtdlMessage *supplied_msg)
2460 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2465 * Message base operation to save a new message to the message store
2466 * (returns new message number)
2468 * This is the back end for CtdlSubmitMsg() and should not be directly
2469 * called by server-side modules.
2472 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2480 * If the message is big, set its body aside for storage elsewhere
2481 * and we hide the message body from the serializer
2483 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2485 holdM = msg->cm_fields[eMesageText];
2486 msg->cm_fields[eMesageText] = NULL;
2487 holdMLen = msg->cm_lengths[eMesageText];
2488 msg->cm_lengths[eMesageText] = 0;
2491 /* Serialize our data structure for storage in the database */
2492 CtdlSerializeMessage(&smr, msg);
2495 /* put the message body back into the message */
2496 msg->cm_fields[eMesageText] = holdM;
2497 msg->cm_lengths[eMesageText] = holdMLen;
2502 cprintf("%d Unable to serialize message\n",
2503 ERROR + INTERNAL_ERROR);
2506 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2512 /* Write our little bundle of joy into the message base */
2513 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2515 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2519 retval = cdb_store(CDB_BIGMSGS,
2526 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2531 /* Free the memory we used for the serialized message */
2538 long send_message(struct CtdlMessage *msg) {
2544 /* Get a new message number */
2545 newmsgid = get_new_message_number();
2547 /* Generate an ID if we don't have one already */
2548 if (CM_IsEmpty(msg, emessageId)) {
2549 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2550 (long unsigned int) time(NULL),
2551 (long unsigned int) newmsgid,
2552 CtdlGetConfigStr("c_fqdn")
2555 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2558 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2564 /* Return the *local* message ID to the caller
2565 * (even if we're storing an incoming network message)
2572 * Serialize a struct CtdlMessage into the format used on disk and network.
2574 * This function loads up a "struct ser_ret" (defined in server.h) which
2575 * contains the length of the serialized message and a pointer to the
2576 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2578 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2579 struct CtdlMessage *msg) /* unserialized msg */
2585 * Check for valid message format
2587 if (CM_IsValidMsg(msg) == 0) {
2588 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2595 for (i=0; i < NDiskFields; ++i)
2596 if (msg->cm_fields[FieldOrder[i]] != NULL)
2597 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2599 ret->ser = malloc(ret->len);
2600 if (ret->ser == NULL) {
2601 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2608 ret->ser[1] = msg->cm_anon_type;
2609 ret->ser[2] = msg->cm_format_type;
2612 for (i=0; i < NDiskFields; ++i) {
2613 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2614 ret->ser[wlen++] = (char)FieldOrder[i];
2616 memcpy(&ret->ser[wlen],
2617 msg->cm_fields[FieldOrder[i]],
2618 msg->cm_lengths[FieldOrder[i]] + 1);
2620 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2624 if (ret->len != wlen) {
2625 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2633 * Check to see if any messages already exist in the current room which
2634 * carry the same Exclusive ID as this one. If any are found, delete them.
2636 void ReplicationChecks(struct CtdlMessage *msg) {
2637 long old_msgnum = (-1L);
2639 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2641 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2643 /* No exclusive id? Don't do anything. */
2644 if (msg == NULL) return;
2645 if (CM_IsEmpty(msg, eExclusiveID)) return;
2647 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2648 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2650 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2651 if (old_msgnum > 0L) {
2652 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2653 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2659 * Save a message to disk and submit it into the delivery system.
2661 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2662 recptypes *recps, /* recipients (if mail) */
2663 const char *force /* force a particular room? */
2665 char hold_rm[ROOMNAMELEN];
2666 char actual_rm[ROOMNAMELEN];
2667 char force_room[ROOMNAMELEN];
2668 char content_type[SIZ]; /* We have to learn this */
2669 char recipient[SIZ];
2670 char bounce_to[1024];
2673 const char *mptr = NULL;
2674 struct ctdluser userbuf;
2676 struct MetaData smi;
2677 char *collected_addresses = NULL;
2678 struct addresses_to_be_filed *aptr = NULL;
2679 StrBuf *saved_rfc822_version = NULL;
2680 int qualified_for_journaling = 0;
2682 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2683 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2685 /* If this message has no timestamp, we take the liberty of
2686 * giving it one, right now.
2688 if (CM_IsEmpty(msg, eTimestamp)) {
2689 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2692 /* If this message has no path, we generate one.
2694 if (CM_IsEmpty(msg, eMessagePath)) {
2695 if (!CM_IsEmpty(msg, eAuthor)) {
2696 CM_CopyField(msg, eMessagePath, eAuthor);
2697 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2698 if (isspace(msg->cm_fields[eMessagePath][a])) {
2699 msg->cm_fields[eMessagePath][a] = ' ';
2704 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2708 if (force == NULL) {
2709 force_room[0] = '\0';
2712 strcpy(force_room, force);
2715 /* Learn about what's inside, because it's what's inside that counts */
2716 if (CM_IsEmpty(msg, eMesageText)) {
2717 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2721 switch (msg->cm_format_type) {
2723 strcpy(content_type, "text/x-citadel-variformat");
2726 strcpy(content_type, "text/plain");
2729 strcpy(content_type, "text/plain");
2730 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2733 safestrncpy(content_type, &mptr[13], sizeof content_type);
2734 striplt(content_type);
2735 aptr = content_type;
2736 while (!IsEmptyStr(aptr)) {
2748 /* Goto the correct room */
2749 room = (recps) ? CC->room.QRname : SENTITEMS;
2750 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2751 strcpy(hold_rm, CC->room.QRname);
2752 strcpy(actual_rm, CC->room.QRname);
2753 if (recps != NULL) {
2754 strcpy(actual_rm, SENTITEMS);
2757 /* If the user is a twit, move to the twit room for posting */
2759 if (CC->user.axlevel == AxProbU) {
2760 strcpy(hold_rm, actual_rm);
2761 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2762 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2766 /* ...or if this message is destined for Aide> then go there. */
2767 if (!IsEmptyStr(force_room)) {
2768 strcpy(actual_rm, force_room);
2771 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2772 if (strcasecmp(actual_rm, CC->room.QRname)) {
2773 /* CtdlGetRoom(&CC->room, actual_rm); */
2774 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2778 * If this message has no O (room) field, generate one.
2780 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2781 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2784 /* Perform "before save" hooks (aborting if any return nonzero) */
2785 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2786 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2789 * If this message has an Exclusive ID, and the room is replication
2790 * checking enabled, then do replication checks.
2792 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2793 ReplicationChecks(msg);
2796 /* Save it to disk */
2797 syslog(LOG_DEBUG, "msgbase: saving to disk");
2798 newmsgid = send_message(msg);
2799 if (newmsgid <= 0L) return(-5);
2801 /* Write a supplemental message info record. This doesn't have to
2802 * be a critical section because nobody else knows about this message
2805 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2806 memset(&smi, 0, sizeof(struct MetaData));
2807 smi.meta_msgnum = newmsgid;
2808 smi.meta_refcount = 0;
2809 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2812 * Measure how big this message will be when rendered as RFC822.
2813 * We do this for two reasons:
2814 * 1. We need the RFC822 length for the new metadata record, so the
2815 * POP and IMAP services don't have to calculate message lengths
2816 * while the user is waiting (multiplied by potentially hundreds
2817 * or thousands of messages).
2818 * 2. If journaling is enabled, we will need an RFC822 version of the
2819 * message to attach to the journalized copy.
2821 if (CC->redirect_buffer != NULL) {
2822 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2825 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2826 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2827 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2828 saved_rfc822_version = CC->redirect_buffer;
2829 CC->redirect_buffer = NULL;
2833 /* Now figure out where to store the pointers */
2834 syslog(LOG_DEBUG, "msgbase: storing pointers");
2836 /* If this is being done by the networker delivering a private
2837 * message, we want to BYPASS saving the sender's copy (because there
2838 * is no local sender; it would otherwise go to the Trashcan).
2840 if ((!CC->internal_pgm) || (recps == NULL)) {
2841 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2842 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2843 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2847 /* For internet mail, drop a copy in the outbound queue room */
2848 if ((recps != NULL) && (recps->num_internet > 0)) {
2849 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2852 /* If other rooms are specified, drop them there too. */
2853 if ((recps != NULL) && (recps->num_room > 0)) {
2854 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2855 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2856 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2857 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2861 /* Bump this user's messages posted counter. */
2862 syslog(LOG_DEBUG, "msgbase: updating user");
2863 CtdlLockGetCurrentUser();
2864 CC->user.posted = CC->user.posted + 1;
2865 CtdlPutCurrentUserLock();
2867 /* Decide where bounces need to be delivered */
2868 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2869 if (CC->logged_in) {
2870 strcpy(bounce_to, CC->user.fullname);
2872 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2873 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2875 recps->bounce_to = bounce_to;
2878 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2880 /* If this is private, local mail, make a copy in the
2881 * recipient's mailbox and bump the reference count.
2883 if ((recps != NULL) && (recps->num_local > 0)) {
2887 pch = recps->recp_local;
2888 recps->recp_local = recipient;
2889 ntokens = num_tokens(pch, '|');
2890 for (i=0; i<ntokens; ++i) {
2891 extract_token(recipient, pch, i, '|', sizeof recipient);
2892 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2893 if (CtdlGetUser(&userbuf, recipient) == 0) {
2894 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2895 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2896 CtdlBumpNewMailCounter(userbuf.usernum);
2897 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2900 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2901 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2904 recps->recp_local = pch;
2907 /* Perform "after save" hooks */
2908 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2910 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2911 CM_FlushField(msg, eVltMsgNum);
2913 /* Go back to the room we started from */
2914 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2915 if (strcasecmp(hold_rm, CC->room.QRname)) {
2916 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2920 * Any addresses to harvest for someone's address book?
2922 if ( (CC->logged_in) && (recps != NULL) ) {
2923 collected_addresses = harvest_collected_addresses(msg);
2926 if (collected_addresses != NULL) {
2927 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2928 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2929 aptr->roomname = strdup(actual_rm);
2930 aptr->collected_addresses = collected_addresses;
2931 begin_critical_section(S_ATBF);
2934 end_critical_section(S_ATBF);
2938 * Determine whether this message qualifies for journaling.
2940 if (!CM_IsEmpty(msg, eJournal)) {
2941 qualified_for_journaling = 0;
2944 if (recps == NULL) {
2945 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2947 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2948 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2951 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2956 * Do we have to perform journaling? If so, hand off the saved
2957 * RFC822 version will be handed off to the journaler for background
2958 * submit. Otherwise, we have to free the memory ourselves.
2960 if (saved_rfc822_version != NULL) {
2961 if (qualified_for_journaling) {
2962 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2965 FreeStrBuf(&saved_rfc822_version);
2969 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2970 recps->bounce_to = NULL;
2978 * Convenience function for generating small administrative messages.
2980 long quickie_message(const char *from,
2981 const char *fromaddr,
2986 const char *subject)
2988 struct CtdlMessage *msg;
2989 recptypes *recp = NULL;
2991 msg = malloc(sizeof(struct CtdlMessage));
2992 memset(msg, 0, sizeof(struct CtdlMessage));
2993 msg->cm_magic = CTDLMESSAGE_MAGIC;
2994 msg->cm_anon_type = MES_NORMAL;
2995 msg->cm_format_type = format_type;
2997 if (!IsEmptyStr(from)) {
2998 CM_SetField(msg, eAuthor, from, -1);
3000 else if (!IsEmptyStr(fromaddr)) {
3002 CM_SetField(msg, eAuthor, fromaddr, -1);
3003 pAt = strchr(msg->cm_fields[eAuthor], '@');
3005 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3009 msg->cm_fields[eAuthor] = strdup("Citadel");
3012 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
3013 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
3014 if (!IsEmptyStr(to)) {
3015 CM_SetField(msg, eRecipient, to, -1);
3016 recp = validate_recipients(to, NULL, 0);
3018 if (!IsEmptyStr(subject)) {
3019 CM_SetField(msg, eMsgSubject, subject, -1);
3021 if (!IsEmptyStr(text)) {
3022 CM_SetField(msg, eMesageText, text, -1);
3025 long msgnum = CtdlSubmitMsg(msg, recp, room);
3027 if (recp != NULL) free_recipients(recp);
3033 * Back end function used by CtdlMakeMessage() and similar functions
3035 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3037 size_t maxlen, /* maximum message length */
3038 StrBuf *exist, /* if non-null, append to it;
3039 exist is ALWAYS freed */
3040 int crlf /* CRLF newlines instead of LF */
3048 LineBuf = NewStrBufPlain(NULL, SIZ);
3049 if (exist == NULL) {
3050 Message = NewStrBufPlain(NULL, 4 * SIZ);
3053 Message = NewStrBufDup(exist);
3056 /* Do we need to change leading ".." to "." for SMTP escaping? */
3057 if ((tlen == 1) && (*terminator == '.')) {
3061 /* read in the lines of message text one by one */
3063 if (CtdlClientGetLine(LineBuf) < 0) {
3066 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3069 if ( (!flushing) && (!finished) ) {
3071 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3074 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3077 /* Unescape SMTP-style input of two dots at the beginning of the line */
3078 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3079 StrBufCutLeft(LineBuf, 1);
3081 StrBufAppendBuf(Message, LineBuf, 0);
3084 /* if we've hit the max msg length, flush the rest */
3085 if (StrLength(Message) >= maxlen) flushing = 1;
3087 } while (!finished);
3088 FreeStrBuf(&LineBuf);
3094 * Back end function used by CtdlMakeMessage() and similar functions
3096 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3098 size_t maxlen, /* maximum message length */
3099 StrBuf *exist, /* if non-null, append to it;
3100 exist is ALWAYS freed */
3101 int crlf /* CRLF newlines instead of LF */
3106 Message = CtdlReadMessageBodyBuf(terminator,
3112 if (Message == NULL)
3115 return SmashStrBuf(&Message);
3119 struct CtdlMessage *CtdlMakeMessage(
3120 struct ctdluser *author, /* author's user structure */
3121 char *recipient, /* NULL if it's not mail */
3122 char *recp_cc, /* NULL if it's not mail */
3123 char *room, /* room where it's going */
3124 int type, /* see MES_ types in header file */
3125 int format_type, /* variformat, plain text, MIME... */
3126 char *fake_name, /* who we're masquerading as */
3127 char *my_email, /* which of my email addresses to use (empty is ok) */
3128 char *subject, /* Subject (optional) */
3129 char *supplied_euid, /* ...or NULL if this is irrelevant */
3130 char *preformatted_text, /* ...or NULL to read text from client */
3131 char *references /* Thread references */
3133 return CtdlMakeMessageLen(
3134 author, /* author's user structure */
3135 recipient, /* NULL if it's not mail */
3136 (recipient)?strlen(recipient) : 0,
3137 recp_cc, /* NULL if it's not mail */
3138 (recp_cc)?strlen(recp_cc): 0,
3139 room, /* room where it's going */
3140 (room)?strlen(room): 0,
3141 type, /* see MES_ types in header file */
3142 format_type, /* variformat, plain text, MIME... */
3143 fake_name, /* who we're masquerading as */
3144 (fake_name)?strlen(fake_name): 0,
3145 my_email, /* which of my email addresses to use (empty is ok) */
3146 (my_email)?strlen(my_email): 0,
3147 subject, /* Subject (optional) */
3148 (subject)?strlen(subject): 0,
3149 supplied_euid, /* ...or NULL if this is irrelevant */
3150 (supplied_euid)?strlen(supplied_euid):0,
3151 preformatted_text, /* ...or NULL to read text from client */
3152 (preformatted_text)?strlen(preformatted_text) : 0,
3153 references, /* Thread references */
3154 (references)?strlen(references):0);
3160 * Build a binary message to be saved on disk.
3161 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3162 * will become part of the message. This means you are no longer
3163 * responsible for managing that memory -- it will be freed along with
3164 * the rest of the fields when CM_Free() is called.)
3166 struct CtdlMessage *CtdlMakeMessageLen(
3167 struct ctdluser *author, /* author's user structure */
3168 char *recipient, /* NULL if it's not mail */
3170 char *recp_cc, /* NULL if it's not mail */
3172 char *room, /* room where it's going */
3174 int type, /* see MES_ types in header file */
3175 int format_type, /* variformat, plain text, MIME... */
3176 char *fake_name, /* who we're masquerading as */
3178 char *my_email, /* which of my email addresses to use (empty is ok) */
3180 char *subject, /* Subject (optional) */
3182 char *supplied_euid, /* ...or NULL if this is irrelevant */
3184 char *preformatted_text, /* ...or NULL to read text from client */
3186 char *references, /* Thread references */
3191 struct CtdlMessage *msg;
3193 StrBuf *FakeEncAuthor = NULL;
3195 msg = malloc(sizeof(struct CtdlMessage));
3196 memset(msg, 0, sizeof(struct CtdlMessage));
3197 msg->cm_magic = CTDLMESSAGE_MAGIC;
3198 msg->cm_anon_type = type;
3199 msg->cm_format_type = format_type;
3201 if (recipient != NULL) rcplen = striplt(recipient);
3202 if (recp_cc != NULL) cclen = striplt(recp_cc);
3204 /* Path or Return-Path */
3206 CM_SetField(msg, eMessagePath, my_email, myelen);
3208 else if (!IsEmptyStr(author->fullname)) {
3209 CM_SetField(msg, eMessagePath, author->fullname, -1);
3211 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3213 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3214 CM_SetField(msg, eTimestamp, buf, blen);
3217 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3220 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3222 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3223 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3224 FreeStrBuf(&FakeAuthor);
3226 if (!!IsEmptyStr(CC->room.QRname)) {
3227 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3228 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3231 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3236 CM_SetField(msg, eRecipient, recipient, rcplen);
3239 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3243 CM_SetField(msg, erFc822Addr, my_email, myelen);
3245 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3246 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3249 if (subject != NULL) {
3251 length = striplt(subject);
3257 while ((subject[i] != '\0') &&
3258 (IsAscii = isascii(subject[i]) != 0 ))
3261 CM_SetField(msg, eMsgSubject, subject, subjlen);
3262 else /* ok, we've got utf8 in the string. */
3265 rfc2047Subj = rfc2047encode(subject, length);
3266 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3273 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3277 CM_SetField(msg, eWeferences, references, reflen);
3280 if (preformatted_text != NULL) {
3281 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3285 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3286 if (MsgBody != NULL) {
3287 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3296 * API function to delete messages which match a set of criteria
3297 * (returns the actual number of messages deleted)
3299 int CtdlDeleteMessages(const char *room_name, // which room
3300 long *dmsgnums, // array of msg numbers to be deleted
3301 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3302 char *content_type // or "" for any. regular expressions expected.
3304 struct ctdlroom qrbuf;
3305 struct cdbdata *cdbfr;
3306 long *msglist = NULL;
3307 long *dellist = NULL;
3310 int num_deleted = 0;
3312 struct MetaData smi;
3315 int need_to_free_re = 0;
3317 if (content_type) if (!IsEmptyStr(content_type)) {
3318 regcomp(&re, content_type, 0);
3319 need_to_free_re = 1;
3321 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3323 /* get room record, obtaining a lock... */
3324 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3325 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3326 if (need_to_free_re) regfree(&re);
3327 return(0); /* room not found */
3329 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3331 if (cdbfr != NULL) {
3332 dellist = malloc(cdbfr->len);
3333 msglist = (long *) cdbfr->ptr;
3334 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3335 num_msgs = cdbfr->len / sizeof(long);
3339 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3340 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3341 int have_more_del = 1;
3343 num_msgs = sort_msglist(msglist, num_msgs);
3344 if (num_dmsgnums > 1)
3345 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3348 StrBuf *dbg = NewStrBuf();
3349 for (i = 0; i < num_dmsgnums; i++)
3350 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3351 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3356 while ((i < num_msgs) && (have_more_del)) {
3359 /* Set/clear a bit for each criterion */
3361 /* 0 messages in the list or a null list means that we are
3362 * interested in deleting any messages which meet the other criteria.
3365 delete_this |= 0x01;
3368 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3373 if (msglist[i] == dmsgnums[j]) {
3374 delete_this |= 0x01;
3377 have_more_del = (j < num_dmsgnums);
3380 if (have_contenttype) {
3381 GetMetaData(&smi, msglist[i]);
3382 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3383 delete_this |= 0x02;
3386 delete_this |= 0x02;
3389 /* Delete message only if all bits are set */
3390 if (delete_this == 0x03) {
3391 dellist[num_deleted++] = msglist[i];
3398 StrBuf *dbg = NewStrBuf();
3399 for (i = 0; i < num_deleted; i++)
3400 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3401 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3405 num_msgs = sort_msglist(msglist, num_msgs);
3406 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3407 msglist, (int)(num_msgs * sizeof(long)));
3410 qrbuf.QRhighest = msglist[num_msgs - 1];
3412 qrbuf.QRhighest = 0;
3414 CtdlPutRoomLock(&qrbuf);
3416 /* Go through the messages we pulled out of the index, and decrement
3417 * their reference counts by 1. If this is the only room the message
3418 * was in, the reference count will reach zero and the message will
3419 * automatically be deleted from the database. We do this in a
3420 * separate pass because there might be plug-in hooks getting called,
3421 * and we don't want that happening during an S_ROOMS critical
3425 for (i=0; i<num_deleted; ++i) {
3426 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3428 AdjRefCountList(dellist, num_deleted, -1);
3430 /* Now free the memory we used, and go away. */
3431 if (msglist != NULL) free(msglist);
3432 if (dellist != NULL) free(dellist);
3433 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3434 if (need_to_free_re) regfree(&re);
3435 return (num_deleted);
3440 * GetMetaData() - Get the supplementary record for a message
3442 void GetMetaData(struct MetaData *smibuf, long msgnum)
3444 struct cdbdata *cdbsmi;
3447 memset(smibuf, 0, sizeof(struct MetaData));
3448 smibuf->meta_msgnum = msgnum;
3449 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3451 /* Use the negative of the message number for its supp record index */
3452 TheIndex = (0L - msgnum);
3454 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3455 if (cdbsmi == NULL) {
3456 return; /* record not found; leave it alone */
3458 memcpy(smibuf, cdbsmi->ptr,
3459 ((cdbsmi->len > sizeof(struct MetaData)) ?
3460 sizeof(struct MetaData) : cdbsmi->len)
3468 * PutMetaData() - (re)write supplementary record for a message
3470 void PutMetaData(struct MetaData *smibuf)
3474 /* Use the negative of the message number for the metadata db index */
3475 TheIndex = (0L - smibuf->meta_msgnum);
3477 cdb_store(CDB_MSGMAIN,
3478 &TheIndex, (int)sizeof(long),
3479 smibuf, (int)sizeof(struct MetaData)
3485 * Convenience function to process a big block of AdjRefCount() operations
3487 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3491 for (i = 0; i < nmsg; i++) {
3492 AdjRefCount(msgnum[i], incr);
3498 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3500 void AdjRefCount(long msgnum, int incr)
3502 struct MetaData smi;
3505 /* This is a *tight* critical section; please keep it that way, as
3506 * it may get called while nested in other critical sections.
3507 * Complicating this any further will surely cause deadlock!
3509 begin_critical_section(S_SUPPMSGMAIN);
3510 GetMetaData(&smi, msgnum);
3511 smi.meta_refcount += incr;
3513 end_critical_section(S_SUPPMSGMAIN);
3514 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3516 /* If the reference count is now zero, delete both the message and its metadata record.
3518 if (smi.meta_refcount == 0) {
3519 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3521 /* Call delete hooks with NULL room to show it has gone altogether */
3522 PerformDeleteHooks(NULL, msgnum);
3524 /* Remove from message base */
3526 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3527 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3529 /* Remove metadata record */
3530 delnum = (0L - msgnum);
3531 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3537 * Write a generic object to this room
3539 * Note: this could be much more efficient. Right now we use two temporary
3540 * files, and still pull the message into memory as with all others.
3542 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3543 char *content_type, /* MIME type of this object */
3544 char *raw_message, /* Data to be written */
3545 off_t raw_length, /* Size of raw_message */
3546 struct ctdluser *is_mailbox, /* Mailbox room? */
3547 int is_binary, /* Is encoding necessary? */
3548 int is_unique, /* Del others of this type? */
3549 unsigned int flags /* Internal save flags */
3551 struct ctdlroom qrbuf;
3552 char roomname[ROOMNAMELEN];
3553 struct CtdlMessage *msg;
3554 StrBuf *encoded_message = NULL;
3556 if (is_mailbox != NULL) {
3557 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3560 safestrncpy(roomname, req_room, sizeof(roomname));
3563 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3566 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3569 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3572 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3573 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3574 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3577 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3580 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3584 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3587 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3590 syslog(LOG_DEBUG, "msgbase: allocating");
3591 msg = malloc(sizeof(struct CtdlMessage));
3592 memset(msg, 0, sizeof(struct CtdlMessage));
3593 msg->cm_magic = CTDLMESSAGE_MAGIC;
3594 msg->cm_anon_type = MES_NORMAL;
3595 msg->cm_format_type = 4;
3596 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3597 CM_SetField(msg, eOriginalRoom, req_room, -1);
3598 msg->cm_flags = flags;
3600 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3602 /* Create the requested room if we have to. */
3603 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3604 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3606 /* If the caller specified this object as unique, delete all
3607 * other objects of this type that are currently in the room.
3610 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3611 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3614 /* Now write the data */
3615 CtdlSubmitMsg(msg, NULL, roomname);
3620 /************************************************************************/
3621 /* MODULE INITIALIZATION */
3622 /************************************************************************/
3624 CTDL_MODULE_INIT(msgbase)
3627 FillMsgKeyLookupTable();
3630 /* return our module id for the log */