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? */
2662 char hold_rm[ROOMNAMELEN];
2663 char actual_rm[ROOMNAMELEN];
2664 char force_room[ROOMNAMELEN];
2665 char content_type[SIZ]; /* We have to learn this */
2666 char recipient[SIZ];
2667 char bounce_to[1024];
2670 const char *mptr = NULL;
2671 struct ctdluser userbuf;
2673 struct MetaData smi;
2674 char *collected_addresses = NULL;
2675 struct addresses_to_be_filed *aptr = NULL;
2676 StrBuf *saved_rfc822_version = NULL;
2677 int qualified_for_journaling = 0;
2679 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2680 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2682 /* If this message has no timestamp, we take the liberty of
2683 * giving it one, right now.
2685 if (CM_IsEmpty(msg, eTimestamp)) {
2686 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2689 /* If this message has no path, we generate one.
2691 if (CM_IsEmpty(msg, eMessagePath)) {
2692 if (!CM_IsEmpty(msg, eAuthor)) {
2693 CM_CopyField(msg, eMessagePath, eAuthor);
2694 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2695 if (isspace(msg->cm_fields[eMessagePath][a])) {
2696 msg->cm_fields[eMessagePath][a] = ' ';
2701 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2705 if (force == NULL) {
2706 force_room[0] = '\0';
2709 strcpy(force_room, force);
2712 /* Learn about what's inside, because it's what's inside that counts */
2713 if (CM_IsEmpty(msg, eMesageText)) {
2714 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2718 switch (msg->cm_format_type) {
2720 strcpy(content_type, "text/x-citadel-variformat");
2723 strcpy(content_type, "text/plain");
2726 strcpy(content_type, "text/plain");
2727 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2730 safestrncpy(content_type, &mptr[13], sizeof content_type);
2731 striplt(content_type);
2732 aptr = content_type;
2733 while (!IsEmptyStr(aptr)) {
2745 /* Goto the correct room */
2746 room = (recps) ? CC->room.QRname : SENTITEMS;
2747 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2748 strcpy(hold_rm, CC->room.QRname);
2749 strcpy(actual_rm, CC->room.QRname);
2750 if (recps != NULL) {
2751 strcpy(actual_rm, SENTITEMS);
2754 /* If the user is a twit, move to the twit room for posting */
2756 if (CC->user.axlevel == AxProbU) {
2757 strcpy(hold_rm, actual_rm);
2758 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2759 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2763 /* ...or if this message is destined for Aide> then go there. */
2764 if (!IsEmptyStr(force_room)) {
2765 strcpy(actual_rm, force_room);
2768 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2769 if (strcasecmp(actual_rm, CC->room.QRname)) {
2770 /* CtdlGetRoom(&CC->room, actual_rm); */
2771 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2775 * If this message has no O (room) field, generate one.
2777 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2778 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2781 /* Perform "before save" hooks (aborting if any return nonzero) */
2782 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2783 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2786 * If this message has an Exclusive ID, and the room is replication
2787 * checking enabled, then do replication checks.
2789 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2790 ReplicationChecks(msg);
2793 /* Save it to disk */
2794 syslog(LOG_DEBUG, "msgbase: saving to disk");
2795 newmsgid = send_message(msg);
2796 if (newmsgid <= 0L) return(-5);
2798 /* Write a supplemental message info record. This doesn't have to
2799 * be a critical section because nobody else knows about this message
2802 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2803 memset(&smi, 0, sizeof(struct MetaData));
2804 smi.meta_msgnum = newmsgid;
2805 smi.meta_refcount = 0;
2806 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2809 * Measure how big this message will be when rendered as RFC822.
2810 * We do this for two reasons:
2811 * 1. We need the RFC822 length for the new metadata record, so the
2812 * POP and IMAP services don't have to calculate message lengths
2813 * while the user is waiting (multiplied by potentially hundreds
2814 * or thousands of messages).
2815 * 2. If journaling is enabled, we will need an RFC822 version of the
2816 * message to attach to the journalized copy.
2818 if (CC->redirect_buffer != NULL) {
2819 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2822 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2823 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2824 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2825 saved_rfc822_version = CC->redirect_buffer;
2826 CC->redirect_buffer = NULL;
2830 /* Now figure out where to store the pointers */
2831 syslog(LOG_DEBUG, "msgbase: storing pointers");
2833 /* If this is being done by the networker delivering a private
2834 * message, we want to BYPASS saving the sender's copy (because there
2835 * is no local sender; it would otherwise go to the Trashcan).
2837 if ((!CC->internal_pgm) || (recps == NULL)) {
2838 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2839 syslog(LOG_ERR, "msgbase: ERROR saving message pointer!");
2840 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2844 /* For internet mail, drop a copy in the outbound queue room */
2845 if ((recps != NULL) && (recps->num_internet > 0)) {
2846 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2849 /* If other rooms are specified, drop them there too. */
2850 if ((recps != NULL) && (recps->num_room > 0)) {
2851 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2852 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2853 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2854 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2858 /* Bump this user's messages posted counter. */
2859 syslog(LOG_DEBUG, "msgbase: updating user");
2860 CtdlLockGetCurrentUser();
2861 CC->user.posted = CC->user.posted + 1;
2862 CtdlPutCurrentUserLock();
2864 /* Decide where bounces need to be delivered */
2865 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2866 if (CC->logged_in) {
2867 strcpy(bounce_to, CC->user.fullname);
2869 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2870 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2872 recps->bounce_to = bounce_to;
2875 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2877 /* If this is private, local mail, make a copy in the
2878 * recipient's mailbox and bump the reference count.
2880 if ((recps != NULL) && (recps->num_local > 0)) {
2884 pch = recps->recp_local;
2885 recps->recp_local = recipient;
2886 ntokens = num_tokens(pch, '|');
2887 for (i=0; i<ntokens; ++i) {
2888 extract_token(recipient, pch, i, '|', sizeof recipient);
2889 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2890 if (CtdlGetUser(&userbuf, recipient) == 0) {
2891 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2892 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2893 CtdlBumpNewMailCounter(userbuf.usernum);
2894 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2897 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2898 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2901 recps->recp_local = pch;
2904 /* Perform "after save" hooks */
2905 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2907 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2908 CM_FlushField(msg, eVltMsgNum);
2910 /* Go back to the room we started from */
2911 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2912 if (strcasecmp(hold_rm, CC->room.QRname)) {
2913 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2917 * Any addresses to harvest for someone's address book?
2919 if ( (CC->logged_in) && (recps != NULL) ) {
2920 collected_addresses = harvest_collected_addresses(msg);
2923 if (collected_addresses != NULL) {
2924 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2925 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2926 aptr->roomname = strdup(actual_rm);
2927 aptr->collected_addresses = collected_addresses;
2928 begin_critical_section(S_ATBF);
2931 end_critical_section(S_ATBF);
2935 * Determine whether this message qualifies for journaling.
2937 if (!CM_IsEmpty(msg, eJournal)) {
2938 qualified_for_journaling = 0;
2941 if (recps == NULL) {
2942 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2944 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2945 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2948 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2953 * Do we have to perform journaling? If so, hand off the saved
2954 * RFC822 version will be handed off to the journaler for background
2955 * submit. Otherwise, we have to free the memory ourselves.
2957 if (saved_rfc822_version != NULL) {
2958 if (qualified_for_journaling) {
2959 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2962 FreeStrBuf(&saved_rfc822_version);
2966 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2967 recps->bounce_to = NULL;
2975 * Convenience function for generating small administrative messages.
2977 long quickie_message(const char *from,
2978 const char *fromaddr,
2983 const char *subject)
2985 struct CtdlMessage *msg;
2986 recptypes *recp = NULL;
2988 msg = malloc(sizeof(struct CtdlMessage));
2989 memset(msg, 0, sizeof(struct CtdlMessage));
2990 msg->cm_magic = CTDLMESSAGE_MAGIC;
2991 msg->cm_anon_type = MES_NORMAL;
2992 msg->cm_format_type = format_type;
2994 if (!IsEmptyStr(from)) {
2995 CM_SetField(msg, eAuthor, from, -1);
2997 else if (!IsEmptyStr(fromaddr)) {
2999 CM_SetField(msg, eAuthor, fromaddr, -1);
3000 pAt = strchr(msg->cm_fields[eAuthor], '@');
3002 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3006 msg->cm_fields[eAuthor] = strdup("Citadel");
3009 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
3010 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
3011 if (!IsEmptyStr(to)) {
3012 CM_SetField(msg, eRecipient, to, -1);
3013 recp = validate_recipients(to, NULL, 0);
3015 if (!IsEmptyStr(subject)) {
3016 CM_SetField(msg, eMsgSubject, subject, -1);
3018 if (!IsEmptyStr(text)) {
3019 CM_SetField(msg, eMesageText, text, -1);
3022 long msgnum = CtdlSubmitMsg(msg, recp, room);
3024 if (recp != NULL) free_recipients(recp);
3030 * Back end function used by CtdlMakeMessage() and similar functions
3032 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3034 size_t maxlen, /* maximum message length */
3035 StrBuf *exist, /* if non-null, append to it;
3036 exist is ALWAYS freed */
3037 int crlf /* CRLF newlines instead of LF */
3045 LineBuf = NewStrBufPlain(NULL, SIZ);
3046 if (exist == NULL) {
3047 Message = NewStrBufPlain(NULL, 4 * SIZ);
3050 Message = NewStrBufDup(exist);
3053 /* Do we need to change leading ".." to "." for SMTP escaping? */
3054 if ((tlen == 1) && (*terminator == '.')) {
3058 /* read in the lines of message text one by one */
3060 if (CtdlClientGetLine(LineBuf) < 0) {
3063 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3066 if ( (!flushing) && (!finished) ) {
3068 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3071 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3074 /* Unescape SMTP-style input of two dots at the beginning of the line */
3075 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3076 StrBufCutLeft(LineBuf, 1);
3078 StrBufAppendBuf(Message, LineBuf, 0);
3081 /* if we've hit the max msg length, flush the rest */
3082 if (StrLength(Message) >= maxlen) flushing = 1;
3084 } while (!finished);
3085 FreeStrBuf(&LineBuf);
3091 * Back end function used by CtdlMakeMessage() and similar functions
3093 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3095 size_t maxlen, /* maximum message length */
3096 StrBuf *exist, /* if non-null, append to it;
3097 exist is ALWAYS freed */
3098 int crlf /* CRLF newlines instead of LF */
3103 Message = CtdlReadMessageBodyBuf(terminator,
3109 if (Message == NULL)
3112 return SmashStrBuf(&Message);
3116 struct CtdlMessage *CtdlMakeMessage(
3117 struct ctdluser *author, /* author's user structure */
3118 char *recipient, /* NULL if it's not mail */
3119 char *recp_cc, /* NULL if it's not mail */
3120 char *room, /* room where it's going */
3121 int type, /* see MES_ types in header file */
3122 int format_type, /* variformat, plain text, MIME... */
3123 char *fake_name, /* who we're masquerading as */
3124 char *my_email, /* which of my email addresses to use (empty is ok) */
3125 char *subject, /* Subject (optional) */
3126 char *supplied_euid, /* ...or NULL if this is irrelevant */
3127 char *preformatted_text, /* ...or NULL to read text from client */
3128 char *references /* Thread references */
3130 return CtdlMakeMessageLen(
3131 author, /* author's user structure */
3132 recipient, /* NULL if it's not mail */
3133 (recipient)?strlen(recipient) : 0,
3134 recp_cc, /* NULL if it's not mail */
3135 (recp_cc)?strlen(recp_cc): 0,
3136 room, /* room where it's going */
3137 (room)?strlen(room): 0,
3138 type, /* see MES_ types in header file */
3139 format_type, /* variformat, plain text, MIME... */
3140 fake_name, /* who we're masquerading as */
3141 (fake_name)?strlen(fake_name): 0,
3142 my_email, /* which of my email addresses to use (empty is ok) */
3143 (my_email)?strlen(my_email): 0,
3144 subject, /* Subject (optional) */
3145 (subject)?strlen(subject): 0,
3146 supplied_euid, /* ...or NULL if this is irrelevant */
3147 (supplied_euid)?strlen(supplied_euid):0,
3148 preformatted_text, /* ...or NULL to read text from client */
3149 (preformatted_text)?strlen(preformatted_text) : 0,
3150 references, /* Thread references */
3151 (references)?strlen(references):0);
3157 * Build a binary message to be saved on disk.
3158 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3159 * will become part of the message. This means you are no longer
3160 * responsible for managing that memory -- it will be freed along with
3161 * the rest of the fields when CM_Free() is called.)
3163 struct CtdlMessage *CtdlMakeMessageLen(
3164 struct ctdluser *author, /* author's user structure */
3165 char *recipient, /* NULL if it's not mail */
3167 char *recp_cc, /* NULL if it's not mail */
3169 char *room, /* room where it's going */
3171 int type, /* see MES_ types in header file */
3172 int format_type, /* variformat, plain text, MIME... */
3173 char *fake_name, /* who we're masquerading as */
3175 char *my_email, /* which of my email addresses to use (empty is ok) */
3177 char *subject, /* Subject (optional) */
3179 char *supplied_euid, /* ...or NULL if this is irrelevant */
3181 char *preformatted_text, /* ...or NULL to read text from client */
3183 char *references, /* Thread references */
3188 struct CtdlMessage *msg;
3190 StrBuf *FakeEncAuthor = NULL;
3192 msg = malloc(sizeof(struct CtdlMessage));
3193 memset(msg, 0, sizeof(struct CtdlMessage));
3194 msg->cm_magic = CTDLMESSAGE_MAGIC;
3195 msg->cm_anon_type = type;
3196 msg->cm_format_type = format_type;
3198 if (recipient != NULL) rcplen = striplt(recipient);
3199 if (recp_cc != NULL) cclen = striplt(recp_cc);
3201 /* Path or Return-Path */
3203 CM_SetField(msg, eMessagePath, my_email, myelen);
3205 else if (!IsEmptyStr(author->fullname)) {
3206 CM_SetField(msg, eMessagePath, author->fullname, -1);
3208 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3210 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3211 CM_SetField(msg, eTimestamp, buf, blen);
3214 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3217 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3219 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3220 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3221 FreeStrBuf(&FakeAuthor);
3223 if (!!IsEmptyStr(CC->room.QRname)) {
3224 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3225 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3228 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3233 CM_SetField(msg, eRecipient, recipient, rcplen);
3236 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3240 CM_SetField(msg, erFc822Addr, my_email, myelen);
3242 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3243 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3246 if (subject != NULL) {
3248 length = striplt(subject);
3254 while ((subject[i] != '\0') &&
3255 (IsAscii = isascii(subject[i]) != 0 ))
3258 CM_SetField(msg, eMsgSubject, subject, subjlen);
3259 else /* ok, we've got utf8 in the string. */
3262 rfc2047Subj = rfc2047encode(subject, length);
3263 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3270 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3274 CM_SetField(msg, eWeferences, references, reflen);
3277 if (preformatted_text != NULL) {
3278 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3282 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3283 if (MsgBody != NULL) {
3284 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3293 * API function to delete messages which match a set of criteria
3294 * (returns the actual number of messages deleted)
3296 int CtdlDeleteMessages(const char *room_name, // which room
3297 long *dmsgnums, // array of msg numbers to be deleted
3298 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3299 char *content_type // or "" for any. regular expressions expected.
3301 struct ctdlroom qrbuf;
3302 struct cdbdata *cdbfr;
3303 long *msglist = NULL;
3304 long *dellist = NULL;
3307 int num_deleted = 0;
3309 struct MetaData smi;
3312 int need_to_free_re = 0;
3314 if (content_type) if (!IsEmptyStr(content_type)) {
3315 regcomp(&re, content_type, 0);
3316 need_to_free_re = 1;
3318 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3320 /* get room record, obtaining a lock... */
3321 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3322 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3323 if (need_to_free_re) regfree(&re);
3324 return(0); /* room not found */
3326 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3328 if (cdbfr != NULL) {
3329 dellist = malloc(cdbfr->len);
3330 msglist = (long *) cdbfr->ptr;
3331 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3332 num_msgs = cdbfr->len / sizeof(long);
3336 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3337 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3338 int have_more_del = 1;
3340 num_msgs = sort_msglist(msglist, num_msgs);
3341 if (num_dmsgnums > 1)
3342 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3345 StrBuf *dbg = NewStrBuf();
3346 for (i = 0; i < num_dmsgnums; i++)
3347 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3348 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3353 while ((i < num_msgs) && (have_more_del)) {
3356 /* Set/clear a bit for each criterion */
3358 /* 0 messages in the list or a null list means that we are
3359 * interested in deleting any messages which meet the other criteria.
3362 delete_this |= 0x01;
3365 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3370 if (msglist[i] == dmsgnums[j]) {
3371 delete_this |= 0x01;
3374 have_more_del = (j < num_dmsgnums);
3377 if (have_contenttype) {
3378 GetMetaData(&smi, msglist[i]);
3379 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3380 delete_this |= 0x02;
3383 delete_this |= 0x02;
3386 /* Delete message only if all bits are set */
3387 if (delete_this == 0x03) {
3388 dellist[num_deleted++] = msglist[i];
3395 StrBuf *dbg = NewStrBuf();
3396 for (i = 0; i < num_deleted; i++)
3397 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3398 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3402 num_msgs = sort_msglist(msglist, num_msgs);
3403 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3404 msglist, (int)(num_msgs * sizeof(long)));
3407 qrbuf.QRhighest = msglist[num_msgs - 1];
3409 qrbuf.QRhighest = 0;
3411 CtdlPutRoomLock(&qrbuf);
3413 /* Go through the messages we pulled out of the index, and decrement
3414 * their reference counts by 1. If this is the only room the message
3415 * was in, the reference count will reach zero and the message will
3416 * automatically be deleted from the database. We do this in a
3417 * separate pass because there might be plug-in hooks getting called,
3418 * and we don't want that happening during an S_ROOMS critical
3422 for (i=0; i<num_deleted; ++i) {
3423 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3425 AdjRefCountList(dellist, num_deleted, -1);
3427 /* Now free the memory we used, and go away. */
3428 if (msglist != NULL) free(msglist);
3429 if (dellist != NULL) free(dellist);
3430 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3431 if (need_to_free_re) regfree(&re);
3432 return (num_deleted);
3437 * GetMetaData() - Get the supplementary record for a message
3439 void GetMetaData(struct MetaData *smibuf, long msgnum)
3441 struct cdbdata *cdbsmi;
3444 memset(smibuf, 0, sizeof(struct MetaData));
3445 smibuf->meta_msgnum = msgnum;
3446 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3448 /* Use the negative of the message number for its supp record index */
3449 TheIndex = (0L - msgnum);
3451 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3452 if (cdbsmi == NULL) {
3453 return; /* record not found; leave it alone */
3455 memcpy(smibuf, cdbsmi->ptr,
3456 ((cdbsmi->len > sizeof(struct MetaData)) ?
3457 sizeof(struct MetaData) : cdbsmi->len)
3465 * PutMetaData() - (re)write supplementary record for a message
3467 void PutMetaData(struct MetaData *smibuf)
3471 /* Use the negative of the message number for the metadata db index */
3472 TheIndex = (0L - smibuf->meta_msgnum);
3474 cdb_store(CDB_MSGMAIN,
3475 &TheIndex, (int)sizeof(long),
3476 smibuf, (int)sizeof(struct MetaData)
3482 * Convenience function to process a big block of AdjRefCount() operations
3484 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3488 for (i = 0; i < nmsg; i++) {
3489 AdjRefCount(msgnum[i], incr);
3495 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3497 void AdjRefCount(long msgnum, int incr)
3499 struct MetaData smi;
3502 /* This is a *tight* critical section; please keep it that way, as
3503 * it may get called while nested in other critical sections.
3504 * Complicating this any further will surely cause deadlock!
3506 begin_critical_section(S_SUPPMSGMAIN);
3507 GetMetaData(&smi, msgnum);
3508 smi.meta_refcount += incr;
3510 end_critical_section(S_SUPPMSGMAIN);
3511 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3513 /* If the reference count is now zero, delete both the message and its metadata record.
3515 if (smi.meta_refcount == 0) {
3516 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3518 /* Call delete hooks with NULL room to show it has gone altogether */
3519 PerformDeleteHooks(NULL, msgnum);
3521 /* Remove from message base */
3523 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3524 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3526 /* Remove metadata record */
3527 delnum = (0L - msgnum);
3528 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3534 * Write a generic object to this room
3536 * Note: this could be much more efficient. Right now we use two temporary
3537 * files, and still pull the message into memory as with all others.
3539 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3540 char *content_type, /* MIME type of this object */
3541 char *raw_message, /* Data to be written */
3542 off_t raw_length, /* Size of raw_message */
3543 struct ctdluser *is_mailbox, /* Mailbox room? */
3544 int is_binary, /* Is encoding necessary? */
3545 int is_unique, /* Del others of this type? */
3546 unsigned int flags /* Internal save flags */
3548 struct ctdlroom qrbuf;
3549 char roomname[ROOMNAMELEN];
3550 struct CtdlMessage *msg;
3551 StrBuf *encoded_message = NULL;
3553 if (is_mailbox != NULL) {
3554 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3557 safestrncpy(roomname, req_room, sizeof(roomname));
3560 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3563 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3566 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3569 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3570 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3571 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3574 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3577 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3581 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3584 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3587 syslog(LOG_DEBUG, "msgbase: allocating");
3588 msg = malloc(sizeof(struct CtdlMessage));
3589 memset(msg, 0, sizeof(struct CtdlMessage));
3590 msg->cm_magic = CTDLMESSAGE_MAGIC;
3591 msg->cm_anon_type = MES_NORMAL;
3592 msg->cm_format_type = 4;
3593 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3594 CM_SetField(msg, eOriginalRoom, req_room, -1);
3595 msg->cm_flags = flags;
3597 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3599 /* Create the requested room if we have to. */
3600 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3601 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3603 /* If the caller specified this object as unique, delete all
3604 * other objects of this type that are currently in the room.
3607 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3608 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3611 /* Now write the data */
3612 CtdlSubmitMsg(msg, NULL, roomname);
3617 /************************************************************************/
3618 /* MODULE INITIALIZATION */
3619 /************************************************************************/
3621 CTDL_MODULE_INIT(msgbase)
3624 FillMsgKeyLookupTable();
3627 /* return our module id for the log */