2 * Implements the message store.
4 * Copyright (c) 1987-2020 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(
1693 struct CtdlMessage *TheMessage,
1694 int do_proto) /* do Citadel protocol responses? */
1698 char display_name[256];
1700 /* begin header processing loop for Citadel message format */
1701 safestrncpy(display_name, "<unknown>", sizeof display_name);
1702 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1703 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1704 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1705 safestrncpy(display_name, "****", sizeof display_name);
1707 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1708 safestrncpy(display_name, "anonymous", sizeof display_name);
1711 safestrncpy(display_name, buf, sizeof display_name);
1713 if ((is_room_aide())
1714 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1715 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1716 size_t tmp = strlen(display_name);
1717 snprintf(&display_name[tmp],
1718 sizeof display_name - tmp,
1723 /* Now spew the header fields in the order we like them. */
1724 for (i=0; i< NDiskFields; ++i) {
1726 Field = FieldOrder[i];
1727 if (Field != eMesageText) {
1728 if ( (!CM_IsEmpty(TheMessage, Field))
1729 && (msgkeys[Field] != NULL) ) {
1730 if ((Field == eenVelopeTo) ||
1731 (Field == eRecipient) ||
1732 (Field == eCarbonCopY)) {
1733 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1735 if (Field == eAuthor) {
1736 if (do_proto) cprintf("%s=%s\n",
1740 /* Masquerade display name if needed */
1743 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1752 void OutputRFC822MsgHeaders(
1753 struct CtdlMessage *TheMessage,
1754 int flags, /* should the message be exported clean */
1755 const char *nl, int nlen,
1756 char *mid, long sizeof_mid,
1757 char *suser, long sizeof_suser,
1758 char *luser, long sizeof_luser,
1759 char *fuser, long sizeof_fuser,
1760 char *snode, long sizeof_snode)
1762 char datestamp[100];
1763 int subject_found = 0;
1770 for (i = 0; i < NDiskFields; ++i) {
1771 if (TheMessage->cm_fields[FieldOrder[i]]) {
1772 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1773 switch (FieldOrder[i]) {
1775 safestrncpy(luser, mptr, sizeof_luser);
1776 safestrncpy(suser, mptr, sizeof_suser);
1779 if ((flags & QP_EADDR) != 0) {
1780 mptr = qp_encode_email_addrs(mptr);
1782 sanitize_truncated_recipient(mptr);
1783 cprintf("CC: %s%s", mptr, nl);
1786 cprintf("Return-Path: %s%s", mptr, nl);
1789 cprintf("List-ID: %s%s", mptr, nl);
1792 if ((flags & QP_EADDR) != 0)
1793 mptr = qp_encode_email_addrs(mptr);
1795 while ((*hptr != '\0') && isspace(*hptr))
1797 if (!IsEmptyStr(hptr))
1798 cprintf("Envelope-To: %s%s", hptr, nl);
1801 cprintf("Subject: %s%s", mptr, nl);
1805 safestrncpy(mid, mptr, sizeof_mid);
1808 safestrncpy(fuser, mptr, sizeof_fuser);
1810 if (haschar(mptr, '@') == 0) {
1811 sanitize_truncated_recipient(mptr);
1812 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1816 if ((flags & QP_EADDR) != 0) {
1817 mptr = qp_encode_email_addrs(mptr);
1819 sanitize_truncated_recipient(mptr);
1820 cprintf("To: %s", mptr);
1825 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1826 cprintf("Date: %s%s", datestamp, nl);
1829 cprintf("References: ");
1830 k = num_tokens(mptr, '|');
1831 for (j=0; j<k; ++j) {
1832 extract_token(buf, mptr, j, '|', sizeof buf);
1833 cprintf("<%s>", buf);
1844 while ((*hptr != '\0') && isspace(*hptr))
1846 if (!IsEmptyStr(hptr))
1847 cprintf("Reply-To: %s%s", mptr, nl);
1859 /* these don't map to mime message headers. */
1862 if (mptr != mpptr) {
1867 if (subject_found == 0) {
1868 cprintf("Subject: (no subject)%s", nl);
1873 void Dump_RFC822HeadersBody(
1874 struct CtdlMessage *TheMessage,
1875 int headers_only, /* eschew the message body? */
1876 int flags, /* should the bessage be exported clean? */
1877 const char *nl, int nlen)
1879 cit_uint8_t prev_ch;
1881 const char *StartOfText = StrBufNOTNULL;
1884 int nllen = strlen(nl);
1888 mptr = TheMessage->cm_fields[eMesageText];
1891 while (*mptr != '\0') {
1892 if (*mptr == '\r') {
1899 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1901 eoh = *(mptr+1) == '\n';
1905 StartOfText = strchr(StartOfText, '\n');
1906 StartOfText = strchr(StartOfText, '\n');
1909 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1910 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1911 ((headers_only != HEADERS_NONE) &&
1912 (headers_only != HEADERS_ONLY))
1914 if (*mptr == '\n') {
1915 memcpy(&outbuf[outlen], nl, nllen);
1917 outbuf[outlen] = '\0';
1920 outbuf[outlen++] = *mptr;
1924 if (flags & ESC_DOT) {
1925 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1926 outbuf[outlen++] = '.';
1931 if (outlen > 1000) {
1932 if (client_write(outbuf, outlen) == -1) {
1933 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1936 lfSent = (outbuf[outlen - 1] == '\n');
1941 client_write(outbuf, outlen);
1942 lfSent = (outbuf[outlen - 1] == '\n');
1945 client_write(nl, nlen);
1949 /* If the format type on disk is 1 (fixed-format), then we want
1950 * everything to be output completely literally ... regardless of
1951 * what message transfer format is in use.
1953 void DumpFormatFixed(
1954 struct CtdlMessage *TheMessage,
1955 int mode, /* how would you like that message? */
1956 const char *nl, int nllen)
1964 mptr = TheMessage->cm_fields[eMesageText];
1966 if (mode == MT_MIME) {
1967 cprintf("Content-type: text/plain\n\n");
1971 while (ch = *mptr++, ch > 0) {
1975 if ((buflen > 250) && (!xlline)){
1979 while ((buflen > 0) &&
1980 (!isspace(buf[buflen])))
1986 mptr -= tbuflen - buflen;
1992 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1993 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1998 memcpy (&buf[buflen], nl, nllen);
2002 if (client_write(buf, buflen) == -1) {
2003 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
2015 if (!IsEmptyStr(buf)) {
2016 cprintf("%s%s", buf, nl);
2022 * Get a message off disk. (returns om_* values found in msgbase.h)
2024 int CtdlOutputPreLoadedMsg(
2025 struct CtdlMessage *TheMessage,
2026 int mode, /* how would you like that message? */
2027 int headers_only, /* eschew the message body? */
2028 int do_proto, /* do Citadel protocol responses? */
2029 int crlf, /* Use CRLF newlines instead of LF? */
2030 int flags /* should the bessage be exported clean? */
2033 const char *nl; /* newline string */
2037 /* Buffers needed for RFC822 translation. These are all filled
2038 * using functions that are bounds-checked, and therefore we can
2039 * make them substantially smaller than SIZ.
2047 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
2048 ((TheMessage == NULL) ? "NULL" : "not null"),
2049 mode, headers_only, do_proto, crlf
2052 strcpy(mid, "unknown");
2053 nl = (crlf ? "\r\n" : "\n");
2054 nlen = crlf ? 2 : 1;
2056 if (!CM_IsValidMsg(TheMessage)) {
2057 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2058 return(om_no_such_msg);
2061 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2062 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2064 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2065 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2068 /* Are we downloading a MIME component? */
2069 if (mode == MT_DOWNLOAD) {
2070 if (TheMessage->cm_format_type != FMT_RFC822) {
2072 cprintf("%d This is not a MIME message.\n",
2073 ERROR + ILLEGAL_VALUE);
2074 } else if (CC->download_fp != NULL) {
2075 if (do_proto) cprintf(
2076 "%d You already have a download open.\n",
2077 ERROR + RESOURCE_BUSY);
2079 /* Parse the message text component */
2080 mime_parser(CM_RANGE(TheMessage, eMesageText),
2081 *mime_download, NULL, NULL, NULL, 0);
2082 /* If there's no file open by this time, the requested
2083 * section wasn't found, so print an error
2085 if (CC->download_fp == NULL) {
2086 if (do_proto) cprintf(
2087 "%d Section %s not found.\n",
2088 ERROR + FILE_NOT_FOUND,
2089 CC->download_desired_section);
2092 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2095 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2096 * in a single server operation instead of opening a download file.
2098 if (mode == MT_SPEW_SECTION) {
2099 if (TheMessage->cm_format_type != FMT_RFC822) {
2101 cprintf("%d This is not a MIME message.\n",
2102 ERROR + ILLEGAL_VALUE);
2104 /* Parse the message text component */
2107 mime_parser(CM_RANGE(TheMessage, eMesageText),
2108 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2109 /* If section wasn't found, print an error
2112 if (do_proto) cprintf(
2113 "%d Section %s not found.\n",
2114 ERROR + FILE_NOT_FOUND,
2115 CC->download_desired_section);
2118 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2121 /* now for the user-mode message reading loops */
2122 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2124 /* Does the caller want to skip the headers? */
2125 if (headers_only == HEADERS_NONE) goto START_TEXT;
2127 /* Tell the client which format type we're using. */
2128 if ( (mode == MT_CITADEL) && (do_proto) ) {
2129 cprintf("type=%d\n", TheMessage->cm_format_type);
2132 /* nhdr=yes means that we're only displaying headers, no body */
2133 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2134 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2137 cprintf("nhdr=yes\n");
2140 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2141 OutputCtdlMsgHeaders(TheMessage, do_proto);
2144 /* begin header processing loop for RFC822 transfer format */
2149 if (mode == MT_RFC822)
2150 OutputRFC822MsgHeaders(
2155 suser, sizeof(suser),
2156 luser, sizeof(luser),
2157 fuser, sizeof(fuser),
2158 snode, sizeof(snode)
2162 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2163 suser[i] = tolower(suser[i]);
2164 if (!isalnum(suser[i])) suser[i]='_';
2167 if (mode == MT_RFC822) {
2168 /* Construct a fun message id */
2169 cprintf("Message-ID: <%s", mid);
2170 if (strchr(mid, '@')==NULL) {
2171 cprintf("@%s", snode);
2175 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2176 cprintf("From: \"----\" <x@x.org>%s", nl);
2178 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2179 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2181 else if (!IsEmptyStr(fuser)) {
2182 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2185 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2188 /* Blank line signifying RFC822 end-of-headers */
2189 if (TheMessage->cm_format_type != FMT_RFC822) {
2194 /* end header processing loop ... at this point, we're in the text */
2196 if (headers_only == HEADERS_FAST) goto DONE;
2198 /* Tell the client about the MIME parts in this message */
2199 if (TheMessage->cm_format_type == FMT_RFC822) {
2200 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2201 memset(&ma, 0, sizeof(struct ma_info));
2202 mime_parser(CM_RANGE(TheMessage, eMesageText),
2203 (do_proto ? *list_this_part : NULL),
2204 (do_proto ? *list_this_pref : NULL),
2205 (do_proto ? *list_this_suff : NULL),
2208 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2209 Dump_RFC822HeadersBody(
2218 if (headers_only == HEADERS_ONLY) {
2222 /* signify start of msg text */
2223 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2224 if (do_proto) cprintf("text\n");
2227 if (TheMessage->cm_format_type == FMT_FIXED)
2230 mode, /* how would you like that message? */
2233 /* If the message on disk is format 0 (Citadel vari-format), we
2234 * output using the formatter at 80 columns. This is the final output
2235 * form if the transfer format is RFC822, but if the transfer format
2236 * is Citadel proprietary, it'll still work, because the indentation
2237 * for new paragraphs is correct and the client will reformat the
2238 * message to the reader's screen width.
2240 if (TheMessage->cm_format_type == FMT_CITADEL) {
2241 if (mode == MT_MIME) {
2242 cprintf("Content-type: text/x-citadel-variformat\n\n");
2244 memfmout(TheMessage->cm_fields[eMesageText], nl);
2247 /* If the message on disk is format 4 (MIME), we've gotta hand it
2248 * off to the MIME parser. The client has already been told that
2249 * this message is format 1 (fixed format), so the callback function
2250 * we use will display those parts as-is.
2252 if (TheMessage->cm_format_type == FMT_RFC822) {
2253 memset(&ma, 0, sizeof(struct ma_info));
2255 if (mode == MT_MIME) {
2256 ma.use_fo_hooks = 0;
2257 strcpy(ma.chosen_part, "1");
2258 ma.chosen_pref = 9999;
2259 ma.dont_decode = CC->msg4_dont_decode;
2260 mime_parser(CM_RANGE(TheMessage, eMesageText),
2261 *choose_preferred, *fixed_output_pre,
2262 *fixed_output_post, (void *)&ma, 1);
2263 mime_parser(CM_RANGE(TheMessage, eMesageText),
2264 *output_preferred, NULL, NULL, (void *)&ma, 1);
2267 ma.use_fo_hooks = 1;
2268 mime_parser(CM_RANGE(TheMessage, eMesageText),
2269 *fixed_output, *fixed_output_pre,
2270 *fixed_output_post, (void *)&ma, 0);
2275 DONE: /* now we're done */
2276 if (do_proto) cprintf("000\n");
2281 * Save one or more message pointers into a specified room
2282 * (Returns 0 for success, nonzero for failure)
2283 * roomname may be NULL to use the current room
2285 * Note that the 'supplied_msg' field may be set to NULL, in which case
2286 * the message will be fetched from disk, by number, if we need to perform
2287 * replication checks. This adds an additional database read, so if the
2288 * caller already has the message in memory then it should be supplied. (Obviously
2289 * this mode of operation only works if we're saving a single message.)
2291 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2292 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2295 char hold_rm[ROOMNAMELEN];
2296 struct cdbdata *cdbfr;
2299 long highest_msg = 0L;
2302 struct CtdlMessage *msg = NULL;
2304 long *msgs_to_be_merged = NULL;
2305 int num_msgs_to_be_merged = 0;
2308 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2309 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2312 strcpy(hold_rm, CC->room.QRname);
2315 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2316 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2317 if (num_newmsgs > 1) supplied_msg = NULL;
2319 /* Now the regular stuff */
2320 if (CtdlGetRoomLock(&CC->room,
2321 ((roomname != NULL) ? roomname : CC->room.QRname) )
2323 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2324 return(ERROR + ROOM_NOT_FOUND);
2328 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2329 num_msgs_to_be_merged = 0;
2332 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2333 if (cdbfr == NULL) {
2337 msglist = (long *) cdbfr->ptr;
2338 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2339 num_msgs = cdbfr->len / sizeof(long);
2344 /* Create a list of msgid's which were supplied by the caller, but do
2345 * not already exist in the target room. It is absolutely taboo to
2346 * have more than one reference to the same message in a room.
2348 for (i=0; i<num_newmsgs; ++i) {
2350 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2351 if (msglist[j] == newmsgidlist[i]) {
2356 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2360 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2363 * Now merge the new messages
2365 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2366 if (msglist == NULL) {
2367 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2368 free(msgs_to_be_merged);
2369 return (ERROR + INTERNAL_ERROR);
2371 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2372 num_msgs += num_msgs_to_be_merged;
2374 /* Sort the message list, so all the msgid's are in order */
2375 num_msgs = sort_msglist(msglist, num_msgs);
2377 /* Determine the highest message number */
2378 highest_msg = msglist[num_msgs - 1];
2380 /* Write it back to disk. */
2381 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2382 msglist, (int)(num_msgs * sizeof(long)));
2384 /* Free up the memory we used. */
2387 /* Update the highest-message pointer and unlock the room. */
2388 CC->room.QRhighest = highest_msg;
2389 CtdlPutRoomLock(&CC->room);
2391 /* Perform replication checks if necessary */
2392 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2393 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2395 for (i=0; i<num_msgs_to_be_merged; ++i) {
2396 msgid = msgs_to_be_merged[i];
2398 if (supplied_msg != NULL) {
2402 msg = CtdlFetchMessage(msgid, 0);
2406 ReplicationChecks(msg);
2408 /* If the message has an Exclusive ID, index that... */
2409 if (!CM_IsEmpty(msg, eExclusiveID)) {
2410 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2413 /* Free up the memory we may have allocated */
2414 if (msg != supplied_msg) {
2423 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2426 /* Submit this room for processing by hooks */
2427 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2428 if (total_roomhook_errors) {
2429 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2432 /* Go back to the room we were in before we wandered here... */
2433 CtdlGetRoom(&CC->room, hold_rm);
2435 /* Bump the reference count for all messages which were merged */
2436 if (!suppress_refcount_adj) {
2437 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2440 /* Free up memory... */
2441 if (msgs_to_be_merged != NULL) {
2442 free(msgs_to_be_merged);
2445 /* Return success. */
2451 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2454 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2455 int do_repl_check, struct CtdlMessage *supplied_msg)
2457 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2462 * Message base operation to save a new message to the message store
2463 * (returns new message number)
2465 * This is the back end for CtdlSubmitMsg() and should not be directly
2466 * called by server-side modules.
2469 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2477 * If the message is big, set its body aside for storage elsewhere
2478 * and we hide the message body from the serializer
2480 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2482 holdM = msg->cm_fields[eMesageText];
2483 msg->cm_fields[eMesageText] = NULL;
2484 holdMLen = msg->cm_lengths[eMesageText];
2485 msg->cm_lengths[eMesageText] = 0;
2488 /* Serialize our data structure for storage in the database */
2489 CtdlSerializeMessage(&smr, msg);
2492 /* put the message body back into the message */
2493 msg->cm_fields[eMesageText] = holdM;
2494 msg->cm_lengths[eMesageText] = holdMLen;
2499 cprintf("%d Unable to serialize message\n",
2500 ERROR + INTERNAL_ERROR);
2503 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2509 /* Write our little bundle of joy into the message base */
2510 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2512 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2516 retval = cdb_store(CDB_BIGMSGS,
2523 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2528 /* Free the memory we used for the serialized message */
2535 long send_message(struct CtdlMessage *msg) {
2541 /* Get a new message number */
2542 newmsgid = get_new_message_number();
2544 /* Generate an ID if we don't have one already */
2545 if (CM_IsEmpty(msg, emessageId)) {
2546 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2547 (long unsigned int) time(NULL),
2548 (long unsigned int) newmsgid,
2549 CtdlGetConfigStr("c_fqdn")
2552 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2555 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2561 /* Return the *local* message ID to the caller
2562 * (even if we're storing an incoming network message)
2569 * Serialize a struct CtdlMessage into the format used on disk and network.
2571 * This function loads up a "struct ser_ret" (defined in server.h) which
2572 * contains the length of the serialized message and a pointer to the
2573 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2575 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2576 struct CtdlMessage *msg) /* unserialized msg */
2582 * Check for valid message format
2584 if (CM_IsValidMsg(msg) == 0) {
2585 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2592 for (i=0; i < NDiskFields; ++i)
2593 if (msg->cm_fields[FieldOrder[i]] != NULL)
2594 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2596 ret->ser = malloc(ret->len);
2597 if (ret->ser == NULL) {
2598 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2605 ret->ser[1] = msg->cm_anon_type;
2606 ret->ser[2] = msg->cm_format_type;
2609 for (i=0; i < NDiskFields; ++i) {
2610 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2611 ret->ser[wlen++] = (char)FieldOrder[i];
2613 memcpy(&ret->ser[wlen],
2614 msg->cm_fields[FieldOrder[i]],
2615 msg->cm_lengths[FieldOrder[i]] + 1);
2617 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2621 if (ret->len != wlen) {
2622 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2630 * Check to see if any messages already exist in the current room which
2631 * carry the same Exclusive ID as this one. If any are found, delete them.
2633 void ReplicationChecks(struct CtdlMessage *msg) {
2634 long old_msgnum = (-1L);
2636 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2638 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2640 /* No exclusive id? Don't do anything. */
2641 if (msg == NULL) return;
2642 if (CM_IsEmpty(msg, eExclusiveID)) return;
2644 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2645 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2647 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2648 if (old_msgnum > 0L) {
2649 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2650 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2656 * Save a message to disk and submit it into the delivery system.
2658 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2659 recptypes *recps, /* recipients (if mail) */
2660 const char *force, /* force a particular room? */
2661 int flags /* should the message be exported clean? */
2663 char hold_rm[ROOMNAMELEN];
2664 char actual_rm[ROOMNAMELEN];
2665 char force_room[ROOMNAMELEN];
2666 char content_type[SIZ]; /* We have to learn this */
2667 char recipient[SIZ];
2668 char bounce_to[1024];
2671 const char *mptr = NULL;
2672 struct ctdluser userbuf;
2674 struct MetaData smi;
2675 char *collected_addresses = NULL;
2676 struct addresses_to_be_filed *aptr = NULL;
2677 StrBuf *saved_rfc822_version = NULL;
2678 int qualified_for_journaling = 0;
2680 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2681 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2683 /* If this message has no timestamp, we take the liberty of
2684 * giving it one, right now.
2686 if (CM_IsEmpty(msg, eTimestamp)) {
2687 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2690 /* If this message has no path, we generate one.
2692 if (CM_IsEmpty(msg, eMessagePath)) {
2693 if (!CM_IsEmpty(msg, eAuthor)) {
2694 CM_CopyField(msg, eMessagePath, eAuthor);
2695 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2696 if (isspace(msg->cm_fields[eMessagePath][a])) {
2697 msg->cm_fields[eMessagePath][a] = ' ';
2702 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2706 if (force == NULL) {
2707 force_room[0] = '\0';
2710 strcpy(force_room, force);
2713 /* Learn about what's inside, because it's what's inside that counts */
2714 if (CM_IsEmpty(msg, eMesageText)) {
2715 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2719 switch (msg->cm_format_type) {
2721 strcpy(content_type, "text/x-citadel-variformat");
2724 strcpy(content_type, "text/plain");
2727 strcpy(content_type, "text/plain");
2728 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2731 safestrncpy(content_type, &mptr[13], sizeof content_type);
2732 striplt(content_type);
2733 aptr = content_type;
2734 while (!IsEmptyStr(aptr)) {
2746 /* Goto the correct room */
2747 room = (recps) ? CC->room.QRname : SENTITEMS;
2748 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2749 strcpy(hold_rm, CC->room.QRname);
2750 strcpy(actual_rm, CC->room.QRname);
2751 if (recps != NULL) {
2752 strcpy(actual_rm, SENTITEMS);
2755 /* If the user is a twit, move to the twit room for posting */
2757 if (CC->user.axlevel == AxProbU) {
2758 strcpy(hold_rm, actual_rm);
2759 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2760 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2764 /* ...or if this message is destined for Aide> then go there. */
2765 if (!IsEmptyStr(force_room)) {
2766 strcpy(actual_rm, force_room);
2769 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2770 if (strcasecmp(actual_rm, CC->room.QRname)) {
2771 /* CtdlGetRoom(&CC->room, actual_rm); */
2772 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2776 * If this message has no O (room) field, generate one.
2778 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2779 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2782 /* Perform "before save" hooks (aborting if any return nonzero) */
2783 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2784 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2787 * If this message has an Exclusive ID, and the room is replication
2788 * checking enabled, then do replication checks.
2790 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2791 ReplicationChecks(msg);
2794 /* Save it to disk */
2795 syslog(LOG_DEBUG, "msgbase: saving to disk");
2796 newmsgid = send_message(msg);
2797 if (newmsgid <= 0L) return(-5);
2799 /* Write a supplemental message info record. This doesn't have to
2800 * be a critical section because nobody else knows about this message
2803 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2804 memset(&smi, 0, sizeof(struct MetaData));
2805 smi.meta_msgnum = newmsgid;
2806 smi.meta_refcount = 0;
2807 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2810 * Measure how big this message will be when rendered as RFC822.
2811 * We do this for two reasons:
2812 * 1. We need the RFC822 length for the new metadata record, so the
2813 * POP and IMAP services don't have to calculate message lengths
2814 * while the user is waiting (multiplied by potentially hundreds
2815 * or thousands of messages).
2816 * 2. If journaling is enabled, we will need an RFC822 version of the
2817 * message to attach to the journalized copy.
2819 if (CC->redirect_buffer != NULL) {
2820 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2823 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2824 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2825 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2826 saved_rfc822_version = CC->redirect_buffer;
2827 CC->redirect_buffer = NULL;
2831 /* Now figure out where to store the pointers */
2832 syslog(LOG_DEBUG, "msgbase: storing pointers");
2834 /* If this is being done by the networker delivering a private
2835 * message, we want to BYPASS saving the sender's copy (because there
2836 * is no local sender; it would otherwise go to the Trashcan).
2838 if ((!CC->internal_pgm) || (recps == NULL)) {
2839 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2840 syslog(LOG_ERR, "msgbase: ERROR saving message pointer!");
2841 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2845 /* For internet mail, drop a copy in the outbound queue room */
2846 if ((recps != NULL) && (recps->num_internet > 0)) {
2847 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2850 /* If other rooms are specified, drop them there too. */
2851 if ((recps != NULL) && (recps->num_room > 0)) {
2852 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2853 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2854 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2855 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2859 /* Bump this user's messages posted counter. */
2860 syslog(LOG_DEBUG, "msgbase: updating user");
2861 CtdlLockGetCurrentUser();
2862 CC->user.posted = CC->user.posted + 1;
2863 CtdlPutCurrentUserLock();
2865 /* Decide where bounces need to be delivered */
2866 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2867 if (CC->logged_in) {
2868 strcpy(bounce_to, CC->user.fullname);
2870 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2871 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2873 recps->bounce_to = bounce_to;
2876 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2878 /* If this is private, local mail, make a copy in the
2879 * recipient's mailbox and bump the reference count.
2881 if ((recps != NULL) && (recps->num_local > 0)) {
2885 pch = recps->recp_local;
2886 recps->recp_local = recipient;
2887 ntokens = num_tokens(pch, '|');
2888 for (i=0; i<ntokens; ++i) {
2889 extract_token(recipient, pch, i, '|', sizeof recipient);
2890 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2891 if (CtdlGetUser(&userbuf, recipient) == 0) {
2892 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2893 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2894 CtdlBumpNewMailCounter(userbuf.usernum);
2895 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2898 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2899 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2902 recps->recp_local = pch;
2905 /* Perform "after save" hooks */
2906 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2908 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2909 CM_FlushField(msg, eVltMsgNum);
2911 /* Go back to the room we started from */
2912 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2913 if (strcasecmp(hold_rm, CC->room.QRname)) {
2914 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2918 * Any addresses to harvest for someone's address book?
2920 if ( (CC->logged_in) && (recps != NULL) ) {
2921 collected_addresses = harvest_collected_addresses(msg);
2924 if (collected_addresses != NULL) {
2925 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2926 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2927 aptr->roomname = strdup(actual_rm);
2928 aptr->collected_addresses = collected_addresses;
2929 begin_critical_section(S_ATBF);
2932 end_critical_section(S_ATBF);
2936 * Determine whether this message qualifies for journaling.
2938 if (!CM_IsEmpty(msg, eJournal)) {
2939 qualified_for_journaling = 0;
2942 if (recps == NULL) {
2943 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2945 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2946 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2949 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2954 * Do we have to perform journaling? If so, hand off the saved
2955 * RFC822 version will be handed off to the journaler for background
2956 * submit. Otherwise, we have to free the memory ourselves.
2958 if (saved_rfc822_version != NULL) {
2959 if (qualified_for_journaling) {
2960 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2963 FreeStrBuf(&saved_rfc822_version);
2967 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2968 recps->bounce_to = NULL;
2976 * Convenience function for generating small administrative messages.
2978 long quickie_message(const char *from,
2979 const char *fromaddr,
2984 const char *subject)
2986 struct CtdlMessage *msg;
2987 recptypes *recp = NULL;
2989 msg = malloc(sizeof(struct CtdlMessage));
2990 memset(msg, 0, sizeof(struct CtdlMessage));
2991 msg->cm_magic = CTDLMESSAGE_MAGIC;
2992 msg->cm_anon_type = MES_NORMAL;
2993 msg->cm_format_type = format_type;
2995 if (!IsEmptyStr(from)) {
2996 CM_SetField(msg, eAuthor, from, -1);
2998 else if (!IsEmptyStr(fromaddr)) {
3000 CM_SetField(msg, eAuthor, fromaddr, -1);
3001 pAt = strchr(msg->cm_fields[eAuthor], '@');
3003 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3007 msg->cm_fields[eAuthor] = strdup("Citadel");
3010 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
3011 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
3012 if (!IsEmptyStr(to)) {
3013 CM_SetField(msg, eRecipient, to, -1);
3014 recp = validate_recipients(to, NULL, 0);
3016 if (!IsEmptyStr(subject)) {
3017 CM_SetField(msg, eMsgSubject, subject, -1);
3019 if (!IsEmptyStr(text)) {
3020 CM_SetField(msg, eMesageText, text, -1);
3023 long msgnum = CtdlSubmitMsg(msg, recp, room, 0);
3025 if (recp != NULL) free_recipients(recp);
3031 * Back end function used by CtdlMakeMessage() and similar functions
3033 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3035 size_t maxlen, /* maximum message length */
3036 StrBuf *exist, /* if non-null, append to it;
3037 exist is ALWAYS freed */
3038 int crlf /* CRLF newlines instead of LF */
3046 LineBuf = NewStrBufPlain(NULL, SIZ);
3047 if (exist == NULL) {
3048 Message = NewStrBufPlain(NULL, 4 * SIZ);
3051 Message = NewStrBufDup(exist);
3054 /* Do we need to change leading ".." to "." for SMTP escaping? */
3055 if ((tlen == 1) && (*terminator == '.')) {
3059 /* read in the lines of message text one by one */
3061 if (CtdlClientGetLine(LineBuf) < 0) {
3064 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3067 if ( (!flushing) && (!finished) ) {
3069 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3072 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3075 /* Unescape SMTP-style input of two dots at the beginning of the line */
3076 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3077 StrBufCutLeft(LineBuf, 1);
3079 StrBufAppendBuf(Message, LineBuf, 0);
3082 /* if we've hit the max msg length, flush the rest */
3083 if (StrLength(Message) >= maxlen) flushing = 1;
3085 } while (!finished);
3086 FreeStrBuf(&LineBuf);
3092 * Back end function used by CtdlMakeMessage() and similar functions
3094 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3096 size_t maxlen, /* maximum message length */
3097 StrBuf *exist, /* if non-null, append to it;
3098 exist is ALWAYS freed */
3099 int crlf /* CRLF newlines instead of LF */
3104 Message = CtdlReadMessageBodyBuf(terminator,
3110 if (Message == NULL)
3113 return SmashStrBuf(&Message);
3117 struct CtdlMessage *CtdlMakeMessage(
3118 struct ctdluser *author, /* author's user structure */
3119 char *recipient, /* NULL if it's not mail */
3120 char *recp_cc, /* NULL if it's not mail */
3121 char *room, /* room where it's going */
3122 int type, /* see MES_ types in header file */
3123 int format_type, /* variformat, plain text, MIME... */
3124 char *fake_name, /* who we're masquerading as */
3125 char *my_email, /* which of my email addresses to use (empty is ok) */
3126 char *subject, /* Subject (optional) */
3127 char *supplied_euid, /* ...or NULL if this is irrelevant */
3128 char *preformatted_text, /* ...or NULL to read text from client */
3129 char *references /* Thread references */
3131 return CtdlMakeMessageLen(
3132 author, /* author's user structure */
3133 recipient, /* NULL if it's not mail */
3134 (recipient)?strlen(recipient) : 0,
3135 recp_cc, /* NULL if it's not mail */
3136 (recp_cc)?strlen(recp_cc): 0,
3137 room, /* room where it's going */
3138 (room)?strlen(room): 0,
3139 type, /* see MES_ types in header file */
3140 format_type, /* variformat, plain text, MIME... */
3141 fake_name, /* who we're masquerading as */
3142 (fake_name)?strlen(fake_name): 0,
3143 my_email, /* which of my email addresses to use (empty is ok) */
3144 (my_email)?strlen(my_email): 0,
3145 subject, /* Subject (optional) */
3146 (subject)?strlen(subject): 0,
3147 supplied_euid, /* ...or NULL if this is irrelevant */
3148 (supplied_euid)?strlen(supplied_euid):0,
3149 preformatted_text, /* ...or NULL to read text from client */
3150 (preformatted_text)?strlen(preformatted_text) : 0,
3151 references, /* Thread references */
3152 (references)?strlen(references):0);
3158 * Build a binary message to be saved on disk.
3159 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3160 * will become part of the message. This means you are no longer
3161 * responsible for managing that memory -- it will be freed along with
3162 * the rest of the fields when CM_Free() is called.)
3164 struct CtdlMessage *CtdlMakeMessageLen(
3165 struct ctdluser *author, /* author's user structure */
3166 char *recipient, /* NULL if it's not mail */
3168 char *recp_cc, /* NULL if it's not mail */
3170 char *room, /* room where it's going */
3172 int type, /* see MES_ types in header file */
3173 int format_type, /* variformat, plain text, MIME... */
3174 char *fake_name, /* who we're masquerading as */
3176 char *my_email, /* which of my email addresses to use (empty is ok) */
3178 char *subject, /* Subject (optional) */
3180 char *supplied_euid, /* ...or NULL if this is irrelevant */
3182 char *preformatted_text, /* ...or NULL to read text from client */
3184 char *references, /* Thread references */
3189 struct CtdlMessage *msg;
3191 StrBuf *FakeEncAuthor = NULL;
3193 msg = malloc(sizeof(struct CtdlMessage));
3194 memset(msg, 0, sizeof(struct CtdlMessage));
3195 msg->cm_magic = CTDLMESSAGE_MAGIC;
3196 msg->cm_anon_type = type;
3197 msg->cm_format_type = format_type;
3199 if (recipient != NULL) rcplen = striplt(recipient);
3200 if (recp_cc != NULL) cclen = striplt(recp_cc);
3202 /* Path or Return-Path */
3204 CM_SetField(msg, eMessagePath, my_email, myelen);
3206 else if (!IsEmptyStr(author->fullname)) {
3207 CM_SetField(msg, eMessagePath, author->fullname, -1);
3209 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3211 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3212 CM_SetField(msg, eTimestamp, buf, blen);
3215 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3218 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3220 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3221 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3222 FreeStrBuf(&FakeAuthor);
3224 if (!!IsEmptyStr(CC->room.QRname)) {
3225 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3226 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3229 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3234 CM_SetField(msg, eRecipient, recipient, rcplen);
3237 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3241 CM_SetField(msg, erFc822Addr, my_email, myelen);
3243 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3244 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3247 if (subject != NULL) {
3249 length = striplt(subject);
3255 while ((subject[i] != '\0') &&
3256 (IsAscii = isascii(subject[i]) != 0 ))
3259 CM_SetField(msg, eMsgSubject, subject, subjlen);
3260 else /* ok, we've got utf8 in the string. */
3263 rfc2047Subj = rfc2047encode(subject, length);
3264 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3271 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3275 CM_SetField(msg, eWeferences, references, reflen);
3278 if (preformatted_text != NULL) {
3279 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3283 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3284 if (MsgBody != NULL) {
3285 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3294 * API function to delete messages which match a set of criteria
3295 * (returns the actual number of messages deleted)
3297 int CtdlDeleteMessages(const char *room_name, // which room
3298 long *dmsgnums, // array of msg numbers to be deleted
3299 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3300 char *content_type // or "" for any. regular expressions expected.
3302 struct ctdlroom qrbuf;
3303 struct cdbdata *cdbfr;
3304 long *msglist = NULL;
3305 long *dellist = NULL;
3308 int num_deleted = 0;
3310 struct MetaData smi;
3313 int need_to_free_re = 0;
3315 if (content_type) if (!IsEmptyStr(content_type)) {
3316 regcomp(&re, content_type, 0);
3317 need_to_free_re = 1;
3319 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3321 /* get room record, obtaining a lock... */
3322 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3323 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3324 if (need_to_free_re) regfree(&re);
3325 return(0); /* room not found */
3327 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3329 if (cdbfr != NULL) {
3330 dellist = malloc(cdbfr->len);
3331 msglist = (long *) cdbfr->ptr;
3332 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3333 num_msgs = cdbfr->len / sizeof(long);
3337 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3338 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3339 int have_more_del = 1;
3341 num_msgs = sort_msglist(msglist, num_msgs);
3342 if (num_dmsgnums > 1)
3343 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3346 StrBuf *dbg = NewStrBuf();
3347 for (i = 0; i < num_dmsgnums; i++)
3348 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3349 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3354 while ((i < num_msgs) && (have_more_del)) {
3357 /* Set/clear a bit for each criterion */
3359 /* 0 messages in the list or a null list means that we are
3360 * interested in deleting any messages which meet the other criteria.
3363 delete_this |= 0x01;
3366 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3371 if (msglist[i] == dmsgnums[j]) {
3372 delete_this |= 0x01;
3375 have_more_del = (j < num_dmsgnums);
3378 if (have_contenttype) {
3379 GetMetaData(&smi, msglist[i]);
3380 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3381 delete_this |= 0x02;
3384 delete_this |= 0x02;
3387 /* Delete message only if all bits are set */
3388 if (delete_this == 0x03) {
3389 dellist[num_deleted++] = msglist[i];
3396 StrBuf *dbg = NewStrBuf();
3397 for (i = 0; i < num_deleted; i++)
3398 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3399 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3403 num_msgs = sort_msglist(msglist, num_msgs);
3404 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3405 msglist, (int)(num_msgs * sizeof(long)));
3408 qrbuf.QRhighest = msglist[num_msgs - 1];
3410 qrbuf.QRhighest = 0;
3412 CtdlPutRoomLock(&qrbuf);
3414 /* Go through the messages we pulled out of the index, and decrement
3415 * their reference counts by 1. If this is the only room the message
3416 * was in, the reference count will reach zero and the message will
3417 * automatically be deleted from the database. We do this in a
3418 * separate pass because there might be plug-in hooks getting called,
3419 * and we don't want that happening during an S_ROOMS critical
3423 for (i=0; i<num_deleted; ++i) {
3424 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3426 AdjRefCountList(dellist, num_deleted, -1);
3428 /* Now free the memory we used, and go away. */
3429 if (msglist != NULL) free(msglist);
3430 if (dellist != NULL) free(dellist);
3431 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3432 if (need_to_free_re) regfree(&re);
3433 return (num_deleted);
3438 * GetMetaData() - Get the supplementary record for a message
3440 void GetMetaData(struct MetaData *smibuf, long msgnum)
3442 struct cdbdata *cdbsmi;
3445 memset(smibuf, 0, sizeof(struct MetaData));
3446 smibuf->meta_msgnum = msgnum;
3447 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3449 /* Use the negative of the message number for its supp record index */
3450 TheIndex = (0L - msgnum);
3452 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3453 if (cdbsmi == NULL) {
3454 return; /* record not found; leave it alone */
3456 memcpy(smibuf, cdbsmi->ptr,
3457 ((cdbsmi->len > sizeof(struct MetaData)) ?
3458 sizeof(struct MetaData) : cdbsmi->len)
3466 * PutMetaData() - (re)write supplementary record for a message
3468 void PutMetaData(struct MetaData *smibuf)
3472 /* Use the negative of the message number for the metadata db index */
3473 TheIndex = (0L - smibuf->meta_msgnum);
3475 cdb_store(CDB_MSGMAIN,
3476 &TheIndex, (int)sizeof(long),
3477 smibuf, (int)sizeof(struct MetaData)
3483 * Convenience function to process a big block of AdjRefCount() operations
3485 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3489 for (i = 0; i < nmsg; i++) {
3490 AdjRefCount(msgnum[i], incr);
3496 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3498 void AdjRefCount(long msgnum, int incr)
3500 struct MetaData smi;
3503 /* This is a *tight* critical section; please keep it that way, as
3504 * it may get called while nested in other critical sections.
3505 * Complicating this any further will surely cause deadlock!
3507 begin_critical_section(S_SUPPMSGMAIN);
3508 GetMetaData(&smi, msgnum);
3509 smi.meta_refcount += incr;
3511 end_critical_section(S_SUPPMSGMAIN);
3512 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3514 /* If the reference count is now zero, delete both the message and its metadata record.
3516 if (smi.meta_refcount == 0) {
3517 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3519 /* Call delete hooks with NULL room to show it has gone altogether */
3520 PerformDeleteHooks(NULL, msgnum);
3522 /* Remove from message base */
3524 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3525 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3527 /* Remove metadata record */
3528 delnum = (0L - msgnum);
3529 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3535 * Write a generic object to this room
3537 * Note: this could be much more efficient. Right now we use two temporary
3538 * files, and still pull the message into memory as with all others.
3540 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3541 char *content_type, /* MIME type of this object */
3542 char *raw_message, /* Data to be written */
3543 off_t raw_length, /* Size of raw_message */
3544 struct ctdluser *is_mailbox, /* Mailbox room? */
3545 int is_binary, /* Is encoding necessary? */
3546 int is_unique, /* Del others of this type? */
3547 unsigned int flags /* Internal save flags */
3549 struct ctdlroom qrbuf;
3550 char roomname[ROOMNAMELEN];
3551 struct CtdlMessage *msg;
3552 StrBuf *encoded_message = NULL;
3554 if (is_mailbox != NULL) {
3555 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3558 safestrncpy(roomname, req_room, sizeof(roomname));
3561 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3564 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3567 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3570 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3571 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3572 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3575 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3578 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3582 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3585 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3588 syslog(LOG_DEBUG, "msgbase: allocating");
3589 msg = malloc(sizeof(struct CtdlMessage));
3590 memset(msg, 0, sizeof(struct CtdlMessage));
3591 msg->cm_magic = CTDLMESSAGE_MAGIC;
3592 msg->cm_anon_type = MES_NORMAL;
3593 msg->cm_format_type = 4;
3594 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3595 CM_SetField(msg, eOriginalRoom, req_room, -1);
3596 msg->cm_flags = flags;
3598 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3600 /* Create the requested room if we have to. */
3601 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3602 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3604 /* If the caller specified this object as unique, delete all
3605 * other objects of this type that are currently in the room.
3608 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3609 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3612 /* Now write the data */
3613 CtdlSubmitMsg(msg, NULL, roomname, 0);
3618 /************************************************************************/
3619 /* MODULE INITIALIZATION */
3620 /************************************************************************/
3622 CTDL_MODULE_INIT(msgbase)
3625 FillMsgKeyLookupTable();
3628 /* return our module id for the log */