2 * Implements the message store.
4 * Copyright (c) 1987-2018 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>
23 #include "ctdl_module.h"
24 #include "citserver.h"
27 #include "clientsocket.h"
31 #include "internet_addressing.h"
32 #include "euidindex.h"
34 #include "journaling.h"
36 struct addresses_to_be_filed *atbf = NULL;
39 * These are the four-character field headers we use when outputting
40 * messages in Citadel format (as opposed to RFC822 format).
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,
50 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
52 "from", /* A -> eAuthor */
53 NULL, /* B -> eBig_message */
54 NULL, /* C -> eRemoteRoom FIXME no more ignet */
55 NULL, /* D -> eDestination FIXME no more ignet */
56 "exti", /* E -> eXclusivID */
57 "rfca", /* F -> erFc822Addr */
59 "hnod", /* H -> eHumanNode FIXME no more ignet */
60 "msgn", /* I -> emessageId */
61 "jrnl", /* J -> eJournal */
62 "rep2", /* K -> eReplyTo */
63 "list", /* L -> eListID */
64 "text", /* M -> eMesageText */
65 "node", /* N -> eNodeName FIXME no more ignet */
66 "room", /* O -> eOriginalRoom */
67 "path", /* P -> eMessagePath */
69 "rcpt", /* R -> eRecipient */
70 "spec", /* S -> eSpecialField FIXME we might not be using this anymore */
71 "time", /* T -> eTimestamp */
72 "subj", /* U -> eMsgSubject */
73 "nvto", /* V -> eenVelopeTo */
74 "wefw", /* W -> eWeferences */
76 "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);
105 eMsgField FieldOrder[] = {
106 /* Important fields */
117 /* Semi-important fields */
123 /* G is not used yet, may become virus signature*/
126 /* Q is not used yet */
129 /* X is not used yet */
130 /* Z is not used yet */
137 /* Message text (MUST be last) */
139 /* Not saved to disk:
144 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
147 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
149 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
153 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
155 if (Msg->cm_fields[which] != NULL) {
156 free (Msg->cm_fields[which]);
158 Msg->cm_fields[which] = malloc(length + 1);
159 memcpy(Msg->cm_fields[which], buf, length);
160 Msg->cm_fields[which][length] = '\0';
161 Msg->cm_lengths[which] = length;
165 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
169 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
170 CM_SetField(Msg, which, buf, len);
174 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
176 if (Msg->cm_fields[WhichToCut] == NULL)
179 if (Msg->cm_lengths[WhichToCut] > maxlen)
181 Msg->cm_fields[WhichToCut][maxlen] = '\0';
182 Msg->cm_lengths[WhichToCut] = maxlen;
187 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
189 if (Msg->cm_fields[which] != NULL)
190 free (Msg->cm_fields[which]);
191 Msg->cm_fields[which] = NULL;
192 Msg->cm_lengths[which] = 0;
196 void CM_Flush(struct CtdlMessage *Msg)
200 if (CM_IsValidMsg(Msg) == 0) {
204 for (i = 0; i < 256; ++i) {
205 CM_FlushField(Msg, i);
210 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
213 if (Msg->cm_fields[WhichToPutTo] != NULL) {
214 free (Msg->cm_fields[WhichToPutTo]);
217 if (Msg->cm_fields[WhichtToCopy] != NULL) {
218 len = Msg->cm_lengths[WhichtToCopy];
219 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
220 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
221 Msg->cm_fields[WhichToPutTo][len] = '\0';
222 Msg->cm_lengths[WhichToPutTo] = len;
225 Msg->cm_fields[WhichToPutTo] = NULL;
226 Msg->cm_lengths[WhichToPutTo] = 0;
231 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
233 if (Msg->cm_fields[which] != NULL) {
238 oldmsgsize = Msg->cm_lengths[which] + 1;
239 newmsgsize = length + oldmsgsize;
241 new = malloc(newmsgsize);
242 memcpy(new, buf, length);
243 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
244 free(Msg->cm_fields[which]);
245 Msg->cm_fields[which] = new;
246 Msg->cm_lengths[which] = newmsgsize - 1;
249 Msg->cm_fields[which] = malloc(length + 1);
250 memcpy(Msg->cm_fields[which], buf, length);
251 Msg->cm_fields[which][length] = '\0';
252 Msg->cm_lengths[which] = length;
257 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
259 if (Msg->cm_fields[which] != NULL) {
260 free (Msg->cm_fields[which]);
263 Msg->cm_fields[which] = *buf;
265 Msg->cm_lengths[which] = length;
269 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
271 if (Msg->cm_fields[which] != NULL) {
272 free (Msg->cm_fields[which]);
275 Msg->cm_lengths[which] = StrLength(*buf);
276 Msg->cm_fields[which] = SmashStrBuf(buf);
280 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
282 if (Msg->cm_fields[which] != NULL) {
283 *retlen = Msg->cm_lengths[which];
284 *ret = Msg->cm_fields[which];
285 Msg->cm_fields[which] = NULL;
286 Msg->cm_lengths[which] = 0;
296 * Returns 1 if the supplied pointer points to a valid Citadel message.
297 * If the pointer is NULL or the magic number check fails, returns 0.
299 int CM_IsValidMsg(struct CtdlMessage *msg) {
303 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
304 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
311 void CM_FreeContents(struct CtdlMessage *msg)
315 for (i = 0; i < 256; ++i)
316 if (msg->cm_fields[i] != NULL) {
317 free(msg->cm_fields[i]);
318 msg->cm_lengths[i] = 0;
321 msg->cm_magic = 0; /* just in case */
326 * 'Destructor' for struct CtdlMessage
328 void CM_Free(struct CtdlMessage *msg)
330 if (CM_IsValidMsg(msg) == 0) {
331 if (msg != NULL) free (msg);
334 CM_FreeContents(msg);
339 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
342 len = OrgMsg->cm_lengths[i];
343 NewMsg->cm_fields[i] = malloc(len + 1);
344 if (NewMsg->cm_fields[i] == NULL) {
347 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
348 NewMsg->cm_fields[i][len] = '\0';
349 NewMsg->cm_lengths[i] = len;
354 struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
357 struct CtdlMessage *NewMsg;
359 if (CM_IsValidMsg(OrgMsg) == 0) {
362 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
363 if (NewMsg == NULL) {
367 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
369 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
371 for (i = 0; i < 256; ++i) {
372 if (OrgMsg->cm_fields[i] != NULL) {
373 if (!CM_DupField(i, OrgMsg, NewMsg)) {
384 /* Determine if a given message matches the fields in a message template.
385 * Return 0 for a successful match.
387 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
390 /* If there aren't any fields in the template, all messages will
393 if (template == NULL) return(0);
395 /* Null messages are bogus. */
396 if (msg == NULL) return(1);
398 for (i='A'; i<='Z'; ++i) {
399 if (template->cm_fields[i] != NULL) {
400 if (msg->cm_fields[i] == NULL) {
401 /* Considered equal if temmplate is empty string */
402 if (IsEmptyStr(template->cm_fields[i])) continue;
405 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
406 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
411 /* All compares succeeded: we have a match! */
417 * Retrieve the "seen" message list for the current room.
419 void CtdlGetSeen(char *buf, int which_set) {
422 /* Learn about the user and room in question */
423 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
425 if (which_set == ctdlsetseen_seen) {
426 safestrncpy(buf, vbuf.v_seen, SIZ);
428 if (which_set == ctdlsetseen_answered) {
429 safestrncpy(buf, vbuf.v_answered, SIZ);
435 * Manipulate the "seen msgs" string (or other message set strings)
437 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
438 int target_setting, int which_set,
439 struct ctdluser *which_user, struct ctdlroom *which_room) {
440 struct cdbdata *cdbfr;
454 char *is_set; /* actually an array of booleans */
456 /* Don't bother doing *anything* if we were passed a list of zero messages */
457 if (num_target_msgnums < 1) {
461 /* If no room was specified, we go with the current room. */
463 which_room = &CC->room;
466 /* If no user was specified, we go with the current user. */
468 which_user = &CC->user;
471 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
472 num_target_msgnums, target_msgnums[0],
473 (target_setting ? "SET" : "CLEAR"),
477 /* Learn about the user and room in question */
478 CtdlGetRelationship(&vbuf, which_user, which_room);
480 /* Load the message list */
481 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
483 msglist = (long *) cdbfr->ptr;
484 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
485 num_msgs = cdbfr->len / sizeof(long);
488 return; /* No messages at all? No further action. */
491 is_set = malloc(num_msgs * sizeof(char));
492 memset(is_set, 0, (num_msgs * sizeof(char)) );
494 /* Decide which message set we're manipulating */
496 case ctdlsetseen_seen:
497 vset = NewStrBufPlain(vbuf.v_seen, -1);
499 case ctdlsetseen_answered:
500 vset = NewStrBufPlain(vbuf.v_answered, -1);
507 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
508 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
509 for (i=0; i<num_msgs; ++i) {
510 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
512 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
513 for (k=0; k<num_target_msgnums; ++k) {
514 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
518 /* Translate the existing sequence set into an array of booleans */
519 setstr = NewStrBuf();
523 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
525 StrBufExtract_token(lostr, setstr, 0, ':');
526 if (StrBufNum_tokens(setstr, ':') >= 2) {
527 StrBufExtract_token(histr, setstr, 1, ':');
531 StrBufAppendBuf(histr, lostr, 0);
534 if (!strcmp(ChrPtr(histr), "*")) {
541 for (i = 0; i < num_msgs; ++i) {
542 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
551 /* Now translate the array of booleans back into a sequence set */
557 for (i=0; i<num_msgs; ++i) {
561 for (k=0; k<num_target_msgnums; ++k) {
562 if (msglist[i] == target_msgnums[k]) {
563 is_seen = target_setting;
567 if ((was_seen == 0) && (is_seen == 1)) {
570 else if ((was_seen == 1) && (is_seen == 0)) {
573 if (StrLength(vset) > 0) {
574 StrBufAppendBufPlain(vset, HKEY(","), 0);
577 StrBufAppendPrintf(vset, "%ld", hi);
580 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
584 if ((is_seen) && (i == num_msgs - 1)) {
585 if (StrLength(vset) > 0) {
586 StrBufAppendBufPlain(vset, HKEY(","), 0);
588 if ((i==0) || (was_seen == 0)) {
589 StrBufAppendPrintf(vset, "%ld", msglist[i]);
592 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
600 * We will have to stuff this string back into a 4096 byte buffer, so if it's
601 * larger than that now, truncate it by removing tokens from the beginning.
602 * The limit of 100 iterations is there to prevent an infinite loop in case
603 * something unexpected happens.
605 int number_of_truncations = 0;
606 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
607 StrBufRemove_token(vset, 0, ',');
608 ++number_of_truncations;
612 * If we're truncating the sequence set of messages marked with the 'seen' flag,
613 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
614 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
616 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
618 first_tok = NewStrBuf();
619 StrBufExtract_token(first_tok, vset, 0, ',');
620 StrBufRemove_token(vset, 0, ',');
622 if (StrBufNum_tokens(first_tok, ':') > 1) {
623 StrBufRemove_token(first_tok, 0, ':');
627 new_set = NewStrBuf();
628 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
629 StrBufAppendBuf(new_set, first_tok, 0);
630 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
631 StrBufAppendBuf(new_set, vset, 0);
634 FreeStrBuf(&first_tok);
638 /* Decide which message set we're manipulating */
640 case ctdlsetseen_seen:
641 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
643 case ctdlsetseen_answered:
644 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
650 CtdlSetRelationship(&vbuf, which_user, which_room);
656 * API function to perform an operation for each qualifying message in the
657 * current room. (Returns the number of messages processed.)
659 int CtdlForEachMessage(int mode, long ref, char *search_string,
661 struct CtdlMessage *compare,
662 ForEachMsgCallback CallBack,
667 struct cdbdata *cdbfr;
668 long *msglist = NULL;
670 int num_processed = 0;
673 struct CtdlMessage *msg = NULL;
676 int printed_lastold = 0;
677 int num_search_msgs = 0;
678 long *search_msgs = NULL;
680 int need_to_free_re = 0;
683 if ((content_type) && (!IsEmptyStr(content_type))) {
684 regcomp(&re, content_type, 0);
688 /* Learn about the user and room in question */
689 if (server_shutting_down) {
690 if (need_to_free_re) regfree(&re);
693 CtdlGetUser(&CC->user, CC->curr_user);
695 if (server_shutting_down) {
696 if (need_to_free_re) regfree(&re);
699 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
701 if (server_shutting_down) {
702 if (need_to_free_re) regfree(&re);
706 /* Load the message list */
707 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
709 if (need_to_free_re) regfree(&re);
710 return 0; /* No messages at all? No further action. */
713 msglist = (long *) cdbfr->ptr;
714 num_msgs = cdbfr->len / sizeof(long);
716 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
717 cdb_free(cdbfr); /* we own this memory now */
720 * Now begin the traversal.
722 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
724 /* If the caller is looking for a specific MIME type, filter
725 * out all messages which are not of the type requested.
727 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
729 /* This call to GetMetaData() sits inside this loop
730 * so that we only do the extra database read per msg
731 * if we need to. Doing the extra read all the time
732 * really kills the server. If we ever need to use
733 * metadata for another search criterion, we need to
734 * move the read somewhere else -- but still be smart
735 * enough to only do the read if the caller has
736 * specified something that will need it.
738 if (server_shutting_down) {
739 if (need_to_free_re) regfree(&re);
743 GetMetaData(&smi, msglist[a]);
745 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
746 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
752 num_msgs = sort_msglist(msglist, num_msgs);
754 /* If a template was supplied, filter out the messages which
755 * don't match. (This could induce some delays!)
758 if (compare != NULL) {
759 for (a = 0; a < num_msgs; ++a) {
760 if (server_shutting_down) {
761 if (need_to_free_re) regfree(&re);
765 msg = CtdlFetchMessage(msglist[a], 1, 1);
767 if (CtdlMsgCmp(msg, compare)) {
776 /* If a search string was specified, get a message list from
777 * the full text index and remove messages which aren't on both
781 * Since the lists are sorted and strictly ascending, and the
782 * output list is guaranteed to be shorter than or equal to the
783 * input list, we overwrite the bottom of the input list. This
784 * eliminates the need to memmove big chunks of the list over and
787 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
789 /* Call search module via hook mechanism.
790 * NULL means use any search function available.
791 * otherwise replace with a char * to name of search routine
793 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
795 if (num_search_msgs > 0) {
799 orig_num_msgs = num_msgs;
801 for (i=0; i<orig_num_msgs; ++i) {
802 for (j=0; j<num_search_msgs; ++j) {
803 if (msglist[i] == search_msgs[j]) {
804 msglist[num_msgs++] = msglist[i];
810 num_msgs = 0; /* No messages qualify */
812 if (search_msgs != NULL) free(search_msgs);
814 /* Now that we've purged messages which don't contain the search
815 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
822 * Now iterate through the message list, according to the
823 * criteria supplied by the caller.
826 for (a = 0; a < num_msgs; ++a) {
827 if (server_shutting_down) {
828 if (need_to_free_re) regfree(&re);
830 return num_processed;
832 thismsg = msglist[a];
833 if (mode == MSGS_ALL) {
837 is_seen = is_msg_in_sequence_set(
838 vbuf.v_seen, thismsg);
839 if (is_seen) lastold = thismsg;
845 || ((mode == MSGS_OLD) && (is_seen))
846 || ((mode == MSGS_NEW) && (!is_seen))
847 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
848 || ((mode == MSGS_FIRST) && (a < ref))
849 || ((mode == MSGS_GT) && (thismsg > ref))
850 || ((mode == MSGS_LT) && (thismsg < ref))
851 || ((mode == MSGS_EQ) && (thismsg == ref))
854 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
856 CallBack(lastold, userdata);
862 CallBack(thismsg, userdata);
867 if (need_to_free_re) regfree(&re);
870 * We cache the most recent msglist in order to do security checks later
872 if (CC->client_socket > 0) {
873 if (CC->cached_msglist != NULL) {
874 free(CC->cached_msglist);
876 CC->cached_msglist = msglist;
877 CC->cached_num_msgs = num_msgs;
883 return num_processed;
888 * memfmout() - Citadel text formatter and paginator.
889 * Although the original purpose of this routine was to format
890 * text to the reader's screen width, all we're really using it
891 * for here is to format text out to 80 columns before sending it
892 * to the client. The client software may reformat it again.
895 char *mptr, /* where are we going to get our text from? */
896 const char *nl /* string to terminate lines with */
899 unsigned char ch = 0;
906 while (ch=*(mptr++), ch != 0) {
909 if (client_write(outbuf, len) == -1) {
910 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
914 if (client_write(nl, nllen) == -1) {
915 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
920 else if (ch == '\r') {
921 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
923 else if (isspace(ch)) {
924 if (column > 72) { /* Beyond 72 columns, break on the next space */
925 if (client_write(outbuf, len) == -1) {
926 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
930 if (client_write(nl, nllen) == -1) {
931 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
944 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
945 if (client_write(outbuf, len) == -1) {
946 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
950 if (client_write(nl, nllen) == -1) {
951 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
959 if (client_write(outbuf, len) == -1) {
960 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
963 client_write(nl, nllen);
970 * Callback function for mime parser that simply lists the part
972 void list_this_part(char *name, char *filename, char *partnum, char *disp,
973 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
974 char *cbid, void *cbuserdata)
978 ma = (struct ma_info *)cbuserdata;
979 if (ma->is_ma == 0) {
980 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
994 * Callback function for multipart prefix
996 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
997 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
998 char *cbid, void *cbuserdata)
1002 ma = (struct ma_info *)cbuserdata;
1003 if (!strcasecmp(cbtype, "multipart/alternative")) {
1007 if (ma->is_ma == 0) {
1008 cprintf("pref=%s|%s\n", partnum, cbtype);
1014 * Callback function for multipart sufffix
1016 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1017 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1018 char *cbid, void *cbuserdata)
1022 ma = (struct ma_info *)cbuserdata;
1023 if (ma->is_ma == 0) {
1024 cprintf("suff=%s|%s\n", partnum, cbtype);
1026 if (!strcasecmp(cbtype, "multipart/alternative")) {
1033 * Callback function for mime parser that opens a section for downloading
1034 * we use serv_files function here:
1036 extern void OpenCmdResult(char *filename, const char *mime_type);
1037 void mime_download(char *name, char *filename, char *partnum, char *disp,
1038 void *content, char *cbtype, char *cbcharset, size_t length,
1039 char *encoding, char *cbid, void *cbuserdata)
1043 /* Silently go away if there's already a download open. */
1044 if (CC->download_fp != NULL)
1048 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1049 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1051 CC->download_fp = tmpfile();
1052 if (CC->download_fp == NULL) {
1053 syslog(LOG_EMERG, "msgbase: mime_download() couldn't write: %m");
1054 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
1058 rv = fwrite(content, length, 1, CC->download_fp);
1060 syslog(LOG_EMERG, "msgbase: mime_download() Couldn't write: %m");
1061 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
1062 fclose(CC->download_fp);
1063 CC->download_fp = NULL;
1066 fflush(CC->download_fp);
1067 rewind(CC->download_fp);
1069 OpenCmdResult(filename, cbtype);
1075 * Callback function for mime parser that outputs a section all at once.
1076 * We can specify the desired section by part number *or* content-id.
1078 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1079 void *content, char *cbtype, char *cbcharset, size_t length,
1080 char *encoding, char *cbid, void *cbuserdata)
1082 int *found_it = (int *)cbuserdata;
1085 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1086 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1089 cprintf("%d %d|-1|%s|%s|%s\n",
1096 client_write(content, length);
1101 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1103 struct CtdlMessage *ret = NULL;
1105 const char *upper_bound;
1107 cit_uint8_t field_header;
1111 upper_bound = Buffer + Length;
1116 /* Parse the three bytes that begin EVERY message on disk.
1117 * The first is always 0xFF, the on-disk magic number.
1118 * The second is the anonymous/public type byte.
1119 * The third is the format type byte (vari, fixed, or MIME).
1123 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1126 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1127 memset(ret, 0, sizeof(struct CtdlMessage));
1129 ret->cm_magic = CTDLMESSAGE_MAGIC;
1130 ret->cm_anon_type = *mptr++; /* Anon type byte */
1131 ret->cm_format_type = *mptr++; /* Format type byte */
1134 * The rest is zero or more arbitrary fields. Load them in.
1135 * We're done when we encounter either a zero-length field or
1136 * have just processed the 'M' (message text) field.
1139 field_header = '\0';
1142 /* work around possibly buggy messages: */
1143 while (field_header == '\0') {
1144 if (mptr >= upper_bound) {
1147 field_header = *mptr++;
1149 if (mptr >= upper_bound) {
1152 which = field_header;
1155 CM_SetField(ret, which, mptr, len);
1157 mptr += len + 1; /* advance to next field */
1159 } while ((mptr < upper_bound) && (field_header != 'M'));
1166 * Load a message from disk into memory.
1167 * This is used by CtdlOutputMsg() and other fetch functions.
1169 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1170 * using the CM_Free(); function.
1172 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body, int run_msg_hooks)
1174 struct cdbdata *dmsgtext;
1175 struct CtdlMessage *ret = NULL;
1177 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1178 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1179 if (dmsgtext == NULL) {
1180 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Failed!", msgnum, with_body);
1184 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
1185 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1186 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1189 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1197 /* Always make sure there's something in the msg text field. If
1198 * it's NULL, the message text is most likely stored separately,
1199 * so go ahead and fetch that. Failing that, just set a dummy
1200 * body so other code doesn't barf.
1202 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1203 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1204 if (dmsgtext != NULL) {
1205 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1209 if (CM_IsEmpty(ret, eMesageText)) {
1210 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1213 /* Perform "before read" hooks (aborting if any return nonzero) */
1214 if (run_msg_hooks && (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0)) {
1224 * Pre callback function for multipart/alternative
1226 * NOTE: this differs from the standard behavior for a reason. Normally when
1227 * displaying multipart/alternative you want to show the _last_ usable
1228 * format in the message. Here we show the _first_ one, because it's
1229 * usually text/plain. Since this set of functions is designed for text
1230 * output to non-MIME-aware clients, this is the desired behavior.
1233 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1234 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1235 char *cbid, void *cbuserdata)
1239 ma = (struct ma_info *)cbuserdata;
1240 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1241 if (!strcasecmp(cbtype, "multipart/alternative")) {
1245 if (!strcasecmp(cbtype, "message/rfc822")) {
1252 * Post callback function for multipart/alternative
1254 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1255 void *content, char *cbtype, char *cbcharset, size_t length,
1256 char *encoding, char *cbid, void *cbuserdata)
1260 ma = (struct ma_info *)cbuserdata;
1261 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1262 if (!strcasecmp(cbtype, "multipart/alternative")) {
1266 if (!strcasecmp(cbtype, "message/rfc822")) {
1273 * Inline callback function for mime parser that wants to display text
1275 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1276 void *content, char *cbtype, char *cbcharset, size_t length,
1277 char *encoding, char *cbid, void *cbuserdata)
1284 ma = (struct ma_info *)cbuserdata;
1287 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1288 partnum, filename, cbtype, (long)length
1292 * If we're in the middle of a multipart/alternative scope and
1293 * we've already printed another section, skip this one.
1295 if ( (ma->is_ma) && (ma->did_print) ) {
1296 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1301 if ( (!strcasecmp(cbtype, "text/plain"))
1302 || (IsEmptyStr(cbtype)) ) {
1305 client_write(wptr, length);
1306 if (wptr[length-1] != '\n') {
1313 if (!strcasecmp(cbtype, "text/html")) {
1314 ptr = html_to_ascii(content, length, 80);
1316 client_write(ptr, wlen);
1317 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1324 if (ma->use_fo_hooks) {
1325 if (PerformFixedOutputHooks(cbtype, content, length)) {
1326 /* above function returns nonzero if it handled the part */
1331 if (strncasecmp(cbtype, "multipart/", 10)) {
1332 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1333 partnum, filename, cbtype, (long)length);
1340 * The client is elegant and sophisticated and wants to be choosy about
1341 * MIME content types, so figure out which multipart/alternative part
1342 * we're going to send.
1344 * We use a system of weights. When we find a part that matches one of the
1345 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1346 * and then set ma->chosen_pref to that MIME type's position in our preference
1347 * list. If we then hit another match, we only replace the first match if
1348 * the preference value is lower.
1350 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1351 void *content, char *cbtype, char *cbcharset, size_t length,
1352 char *encoding, char *cbid, void *cbuserdata)
1358 ma = (struct ma_info *)cbuserdata;
1360 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1361 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1362 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1363 if (i < ma->chosen_pref) {
1364 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1365 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1366 ma->chosen_pref = i;
1374 * Now that we've chosen our preferred part, output it.
1376 void output_preferred(char *name,
1390 int add_newline = 0;
1393 char *decoded = NULL;
1394 size_t bytes_decoded;
1397 ma = (struct ma_info *)cbuserdata;
1399 /* This is not the MIME part you're looking for... */
1400 if (strcasecmp(partnum, ma->chosen_part)) return;
1402 /* If the content-type of this part is in our preferred formats
1403 * list, we can simply output it verbatim.
1405 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1406 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1407 if (!strcasecmp(buf, cbtype)) {
1408 /* Yeah! Go! W00t!! */
1409 if (ma->dont_decode == 0)
1410 rc = mime_decode_now (content,
1416 break; /* Give us the chance, maybe theres another one. */
1418 if (rc == 0) text_content = (char *)content;
1420 text_content = decoded;
1421 length = bytes_decoded;
1424 if (text_content[length-1] != '\n') {
1427 cprintf("Content-type: %s", cbtype);
1428 if (!IsEmptyStr(cbcharset)) {
1429 cprintf("; charset=%s", cbcharset);
1431 cprintf("\nContent-length: %d\n",
1432 (int)(length + add_newline) );
1433 if (!IsEmptyStr(encoding)) {
1434 cprintf("Content-transfer-encoding: %s\n", encoding);
1437 cprintf("Content-transfer-encoding: 7bit\n");
1439 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1441 if (client_write(text_content, length) == -1)
1443 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1446 if (add_newline) cprintf("\n");
1447 if (decoded != NULL) free(decoded);
1452 /* No translations required or possible: output as text/plain */
1453 cprintf("Content-type: text/plain\n\n");
1455 if (ma->dont_decode == 0)
1456 rc = mime_decode_now (content,
1462 return; /* Give us the chance, maybe theres another one. */
1464 if (rc == 0) text_content = (char *)content;
1466 text_content = decoded;
1467 length = bytes_decoded;
1470 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1471 length, encoding, cbid, cbuserdata);
1472 if (decoded != NULL) free(decoded);
1477 char desired_section[64];
1484 * Callback function for
1486 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1487 void *content, char *cbtype, char *cbcharset, size_t length,
1488 char *encoding, char *cbid, void *cbuserdata)
1490 struct encapmsg *encap;
1492 encap = (struct encapmsg *)cbuserdata;
1494 /* Only proceed if this is the desired section... */
1495 if (!strcasecmp(encap->desired_section, partnum)) {
1496 encap->msglen = length;
1497 encap->msg = malloc(length + 2);
1498 memcpy(encap->msg, content, length);
1505 * Determine whether the specified message exists in the cached_msglist
1506 * (This is a security check)
1508 int check_cached_msglist(long msgnum) {
1510 /* cases in which we skip the check */
1511 if (!CC) return om_ok; /* not a session */
1512 if (CC->client_socket <= 0) return om_ok; /* not a client session */
1513 if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1514 if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1517 /* Do a binary search within the cached_msglist for the requested msgnum */
1519 int max = (CC->cached_num_msgs - 1);
1521 while (max >= min) {
1522 int middle = min + (max-min) / 2 ;
1523 if (msgnum == CC->cached_msglist[middle]) {
1526 if (msgnum > CC->cached_msglist[middle]) {
1534 return om_access_denied;
1539 * Get a message off disk. (returns om_* values found in msgbase.h)
1542 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1543 int mode, /* how would you like that message? */
1544 int headers_only, /* eschew the message body? */
1545 int do_proto, /* do Citadel protocol responses? */
1546 int crlf, /* Use CRLF newlines instead of LF? */
1547 char *section, /* NULL or a message/rfc822 section */
1548 int flags, /* various flags; see msgbase.h */
1553 struct CtdlMessage *TheMessage = NULL;
1554 int retcode = CIT_OK;
1555 struct encapmsg encap;
1558 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1560 (section ? section : "<>")
1563 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1566 if (r == om_not_logged_in) {
1567 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1570 cprintf("%d An unknown error has occurred.\n", ERROR);
1577 * Check to make sure the message is actually IN this room
1579 r = check_cached_msglist(msg_num);
1580 if (r == om_access_denied) {
1581 /* Not in the cache? We get ONE shot to check it again. */
1582 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1583 r = check_cached_msglist(msg_num);
1586 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1587 msg_num, CC->room.QRname
1590 if (r == om_access_denied) {
1591 cprintf("%d message %ld was not found in this room\n",
1592 ERROR + HIGHER_ACCESS_REQUIRED,
1601 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1602 * request that we don't even bother loading the body into memory.
1604 if (headers_only == HEADERS_FAST) {
1605 TheMessage = CtdlFetchMessage(msg_num, 0, 1);
1608 TheMessage = CtdlFetchMessage(msg_num, 1, 1);
1611 if (TheMessage == NULL) {
1612 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1613 ERROR + MESSAGE_NOT_FOUND, msg_num);
1614 return(om_no_such_msg);
1617 /* Here is the weird form of this command, to process only an
1618 * encapsulated message/rfc822 section.
1620 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1621 memset(&encap, 0, sizeof encap);
1622 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1623 mime_parser(CM_RANGE(TheMessage, eMesageText),
1624 *extract_encapsulated_message,
1625 NULL, NULL, (void *)&encap, 0
1628 if ((Author != NULL) && (*Author == NULL))
1631 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1633 if ((Address != NULL) && (*Address == NULL))
1636 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1638 if ((MessageID != NULL) && (*MessageID == NULL))
1641 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1643 CM_Free(TheMessage);
1647 encap.msg[encap.msglen] = 0;
1648 TheMessage = convert_internet_message(encap.msg);
1649 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1651 /* Now we let it fall through to the bottom of this
1652 * function, because TheMessage now contains the
1653 * encapsulated message instead of the top-level
1654 * message. Isn't that neat?
1659 cprintf("%d msg %ld has no part %s\n",
1660 ERROR + MESSAGE_NOT_FOUND,
1664 retcode = om_no_such_msg;
1669 /* Ok, output the message now */
1670 if (retcode == CIT_OK)
1671 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1672 if ((Author != NULL) && (*Author == NULL))
1675 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1677 if ((Address != NULL) && (*Address == NULL))
1680 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1682 if ((MessageID != NULL) && (*MessageID == NULL))
1685 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1688 CM_Free(TheMessage);
1694 void OutputCtdlMsgHeaders(
1695 struct CtdlMessage *TheMessage,
1696 int do_proto) /* do Citadel protocol responses? */
1701 char display_name[256];
1703 /* begin header processing loop for Citadel message format */
1704 safestrncpy(display_name, "<unknown>", sizeof display_name);
1705 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1706 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1707 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1708 safestrncpy(display_name, "****", sizeof display_name);
1710 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1711 safestrncpy(display_name, "anonymous", sizeof display_name);
1714 safestrncpy(display_name, buf, sizeof display_name);
1716 if ((is_room_aide())
1717 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1718 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1719 size_t tmp = strlen(display_name);
1720 snprintf(&display_name[tmp],
1721 sizeof display_name - tmp,
1726 /* Don't show Internet address for users on the
1727 * local Citadel network.
1730 if (!CM_IsEmpty(TheMessage, eNodeName) && (haschar(TheMessage->cm_fields[eNodeName], '.') == 0)) {
1734 /* Now spew the header fields in the order we like them. */
1735 for (i=0; i< NDiskFields; ++i) {
1737 Field = FieldOrder[i];
1738 if (Field != eMesageText) {
1739 if ( (!CM_IsEmpty(TheMessage, Field))
1740 && (msgkeys[Field] != NULL) ) {
1741 if ((Field == eenVelopeTo) ||
1742 (Field == eRecipient) ||
1743 (Field == eCarbonCopY)) {
1744 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1746 if (Field == eAuthor) {
1747 if (do_proto) cprintf("%s=%s\n",
1751 else if ((Field == erFc822Addr) && (suppress_f)) {
1754 /* Masquerade display name if needed */
1757 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1766 void OutputRFC822MsgHeaders(
1767 struct CtdlMessage *TheMessage,
1768 int flags, /* should the bessage be exported clean */
1769 const char *nl, int nlen,
1770 char *mid, long sizeof_mid,
1771 char *suser, long sizeof_suser,
1772 char *luser, long sizeof_luser,
1773 char *fuser, long sizeof_fuser,
1774 char *snode, long sizeof_snode)
1776 char datestamp[100];
1777 int subject_found = 0;
1784 for (i = 0; i < NDiskFields; ++i) {
1785 if (TheMessage->cm_fields[FieldOrder[i]]) {
1786 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1787 switch (FieldOrder[i]) {
1789 safestrncpy(luser, mptr, sizeof_luser);
1790 safestrncpy(suser, mptr, sizeof_suser);
1793 if ((flags & QP_EADDR) != 0) {
1794 mptr = qp_encode_email_addrs(mptr);
1796 sanitize_truncated_recipient(mptr);
1797 cprintf("CC: %s%s", mptr, nl);
1800 cprintf("Return-Path: %s%s", mptr, nl);
1803 cprintf("List-ID: %s%s", mptr, nl);
1806 if ((flags & QP_EADDR) != 0)
1807 mptr = qp_encode_email_addrs(mptr);
1809 while ((*hptr != '\0') && isspace(*hptr))
1811 if (!IsEmptyStr(hptr))
1812 cprintf("Envelope-To: %s%s", hptr, nl);
1815 cprintf("Subject: %s%s", mptr, nl);
1819 safestrncpy(mid, mptr, sizeof_mid);
1822 safestrncpy(fuser, mptr, sizeof_fuser);
1824 safestrncpy(snode, mptr, sizeof_snode);
1827 if (haschar(mptr, '@') == 0) {
1828 sanitize_truncated_recipient(mptr);
1829 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1833 if ((flags & QP_EADDR) != 0) {
1834 mptr = qp_encode_email_addrs(mptr);
1836 sanitize_truncated_recipient(mptr);
1837 cprintf("To: %s", mptr);
1842 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1843 cprintf("Date: %s%s", datestamp, nl);
1846 cprintf("References: ");
1847 k = num_tokens(mptr, '|');
1848 for (j=0; j<k; ++j) {
1849 extract_token(buf, mptr, j, '|', sizeof buf);
1850 cprintf("<%s>", buf);
1861 while ((*hptr != '\0') && isspace(*hptr))
1863 if (!IsEmptyStr(hptr))
1864 cprintf("Reply-To: %s%s", mptr, nl);
1880 /* these don't map to mime message headers. */
1883 if (mptr != mpptr) {
1888 if (subject_found == 0) {
1889 cprintf("Subject: (no subject)%s", nl);
1894 void Dump_RFC822HeadersBody(
1895 struct CtdlMessage *TheMessage,
1896 int headers_only, /* eschew the message body? */
1897 int flags, /* should the bessage be exported clean? */
1898 const char *nl, int nlen)
1900 cit_uint8_t prev_ch;
1902 const char *StartOfText = StrBufNOTNULL;
1905 int nllen = strlen(nl);
1909 mptr = TheMessage->cm_fields[eMesageText];
1912 while (*mptr != '\0') {
1913 if (*mptr == '\r') {
1920 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1922 eoh = *(mptr+1) == '\n';
1926 StartOfText = strchr(StartOfText, '\n');
1927 StartOfText = strchr(StartOfText, '\n');
1930 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1931 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1932 ((headers_only != HEADERS_NONE) &&
1933 (headers_only != HEADERS_ONLY))
1935 if (*mptr == '\n') {
1936 memcpy(&outbuf[outlen], nl, nllen);
1938 outbuf[outlen] = '\0';
1941 outbuf[outlen++] = *mptr;
1945 if (flags & ESC_DOT) {
1946 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1947 outbuf[outlen++] = '.';
1952 if (outlen > 1000) {
1953 if (client_write(outbuf, outlen) == -1) {
1954 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1957 lfSent = (outbuf[outlen - 1] == '\n');
1962 client_write(outbuf, outlen);
1963 lfSent = (outbuf[outlen - 1] == '\n');
1966 client_write(nl, nlen);
1970 /* If the format type on disk is 1 (fixed-format), then we want
1971 * everything to be output completely literally ... regardless of
1972 * what message transfer format is in use.
1974 void DumpFormatFixed(
1975 struct CtdlMessage *TheMessage,
1976 int mode, /* how would you like that message? */
1977 const char *nl, int nllen)
1985 mptr = TheMessage->cm_fields[eMesageText];
1987 if (mode == MT_MIME) {
1988 cprintf("Content-type: text/plain\n\n");
1992 while (ch = *mptr++, ch > 0) {
1996 if ((buflen > 250) && (!xlline)){
2000 while ((buflen > 0) &&
2001 (!isspace(buf[buflen])))
2007 mptr -= tbuflen - buflen;
2013 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
2014 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
2019 memcpy (&buf[buflen], nl, nllen);
2023 if (client_write(buf, buflen) == -1) {
2024 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
2036 if (!IsEmptyStr(buf)) {
2037 cprintf("%s%s", buf, nl);
2043 * Get a message off disk. (returns om_* values found in msgbase.h)
2045 int CtdlOutputPreLoadedMsg(
2046 struct CtdlMessage *TheMessage,
2047 int mode, /* how would you like that message? */
2048 int headers_only, /* eschew the message body? */
2049 int do_proto, /* do Citadel protocol responses? */
2050 int crlf, /* Use CRLF newlines instead of LF? */
2051 int flags /* should the bessage be exported clean? */
2054 const char *nl; /* newline string */
2058 /* Buffers needed for RFC822 translation. These are all filled
2059 * using functions that are bounds-checked, and therefore we can
2060 * make them substantially smaller than SIZ.
2068 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
2069 ((TheMessage == NULL) ? "NULL" : "not null"),
2070 mode, headers_only, do_proto, crlf
2073 strcpy(mid, "unknown");
2074 nl = (crlf ? "\r\n" : "\n");
2075 nlen = crlf ? 2 : 1;
2077 if (!CM_IsValidMsg(TheMessage)) {
2078 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2079 return(om_no_such_msg);
2082 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2083 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2085 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2086 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2089 /* Are we downloading a MIME component? */
2090 if (mode == MT_DOWNLOAD) {
2091 if (TheMessage->cm_format_type != FMT_RFC822) {
2093 cprintf("%d This is not a MIME message.\n",
2094 ERROR + ILLEGAL_VALUE);
2095 } else if (CC->download_fp != NULL) {
2096 if (do_proto) cprintf(
2097 "%d You already have a download open.\n",
2098 ERROR + RESOURCE_BUSY);
2100 /* Parse the message text component */
2101 mime_parser(CM_RANGE(TheMessage, eMesageText),
2102 *mime_download, NULL, NULL, NULL, 0);
2103 /* If there's no file open by this time, the requested
2104 * section wasn't found, so print an error
2106 if (CC->download_fp == NULL) {
2107 if (do_proto) cprintf(
2108 "%d Section %s not found.\n",
2109 ERROR + FILE_NOT_FOUND,
2110 CC->download_desired_section);
2113 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2116 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2117 * in a single server operation instead of opening a download file.
2119 if (mode == MT_SPEW_SECTION) {
2120 if (TheMessage->cm_format_type != FMT_RFC822) {
2122 cprintf("%d This is not a MIME message.\n",
2123 ERROR + ILLEGAL_VALUE);
2125 /* Parse the message text component */
2128 mime_parser(CM_RANGE(TheMessage, eMesageText),
2129 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2130 /* If section wasn't found, print an error
2133 if (do_proto) cprintf(
2134 "%d Section %s not found.\n",
2135 ERROR + FILE_NOT_FOUND,
2136 CC->download_desired_section);
2139 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2142 /* now for the user-mode message reading loops */
2143 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2145 /* Does the caller want to skip the headers? */
2146 if (headers_only == HEADERS_NONE) goto START_TEXT;
2148 /* Tell the client which format type we're using. */
2149 if ( (mode == MT_CITADEL) && (do_proto) ) {
2150 cprintf("type=%d\n", TheMessage->cm_format_type);
2153 /* nhdr=yes means that we're only displaying headers, no body */
2154 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2155 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2158 cprintf("nhdr=yes\n");
2161 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2162 OutputCtdlMsgHeaders(TheMessage, do_proto);
2165 /* begin header processing loop for RFC822 transfer format */
2169 memcpy(snode, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")) + 1);
2170 if (mode == MT_RFC822)
2171 OutputRFC822MsgHeaders(
2176 suser, sizeof(suser),
2177 luser, sizeof(luser),
2178 fuser, sizeof(fuser),
2179 snode, sizeof(snode)
2183 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2184 suser[i] = tolower(suser[i]);
2185 if (!isalnum(suser[i])) suser[i]='_';
2188 if (mode == MT_RFC822) {
2189 if (!strcasecmp(snode, NODENAME)) {
2190 safestrncpy(snode, FQDN, sizeof snode);
2193 /* Construct a fun message id */
2194 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2195 if (strchr(mid, '@')==NULL) {
2196 cprintf("@%s", snode);
2200 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2201 cprintf("From: \"----\" <x@x.org>%s", nl);
2203 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2204 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2206 else if (!IsEmptyStr(fuser)) {
2207 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2210 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2213 /* Blank line signifying RFC822 end-of-headers */
2214 if (TheMessage->cm_format_type != FMT_RFC822) {
2219 /* end header processing loop ... at this point, we're in the text */
2221 if (headers_only == HEADERS_FAST) goto DONE;
2223 /* Tell the client about the MIME parts in this message */
2224 if (TheMessage->cm_format_type == FMT_RFC822) {
2225 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2226 memset(&ma, 0, sizeof(struct ma_info));
2227 mime_parser(CM_RANGE(TheMessage, eMesageText),
2228 (do_proto ? *list_this_part : NULL),
2229 (do_proto ? *list_this_pref : NULL),
2230 (do_proto ? *list_this_suff : NULL),
2233 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2234 Dump_RFC822HeadersBody(
2243 if (headers_only == HEADERS_ONLY) {
2247 /* signify start of msg text */
2248 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2249 if (do_proto) cprintf("text\n");
2252 if (TheMessage->cm_format_type == FMT_FIXED)
2255 mode, /* how would you like that message? */
2258 /* If the message on disk is format 0 (Citadel vari-format), we
2259 * output using the formatter at 80 columns. This is the final output
2260 * form if the transfer format is RFC822, but if the transfer format
2261 * is Citadel proprietary, it'll still work, because the indentation
2262 * for new paragraphs is correct and the client will reformat the
2263 * message to the reader's screen width.
2265 if (TheMessage->cm_format_type == FMT_CITADEL) {
2266 if (mode == MT_MIME) {
2267 cprintf("Content-type: text/x-citadel-variformat\n\n");
2269 memfmout(TheMessage->cm_fields[eMesageText], nl);
2272 /* If the message on disk is format 4 (MIME), we've gotta hand it
2273 * off to the MIME parser. The client has already been told that
2274 * this message is format 1 (fixed format), so the callback function
2275 * we use will display those parts as-is.
2277 if (TheMessage->cm_format_type == FMT_RFC822) {
2278 memset(&ma, 0, sizeof(struct ma_info));
2280 if (mode == MT_MIME) {
2281 ma.use_fo_hooks = 0;
2282 strcpy(ma.chosen_part, "1");
2283 ma.chosen_pref = 9999;
2284 ma.dont_decode = CC->msg4_dont_decode;
2285 mime_parser(CM_RANGE(TheMessage, eMesageText),
2286 *choose_preferred, *fixed_output_pre,
2287 *fixed_output_post, (void *)&ma, 1);
2288 mime_parser(CM_RANGE(TheMessage, eMesageText),
2289 *output_preferred, NULL, NULL, (void *)&ma, 1);
2292 ma.use_fo_hooks = 1;
2293 mime_parser(CM_RANGE(TheMessage, eMesageText),
2294 *fixed_output, *fixed_output_pre,
2295 *fixed_output_post, (void *)&ma, 0);
2300 DONE: /* now we're done */
2301 if (do_proto) cprintf("000\n");
2306 * Save one or more message pointers into a specified room
2307 * (Returns 0 for success, nonzero for failure)
2308 * roomname may be NULL to use the current room
2310 * Note that the 'supplied_msg' field may be set to NULL, in which case
2311 * the message will be fetched from disk, by number, if we need to perform
2312 * replication checks. This adds an additional database read, so if the
2313 * caller already has the message in memory then it should be supplied. (Obviously
2314 * this mode of operation only works if we're saving a single message.)
2316 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2317 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2320 char hold_rm[ROOMNAMELEN];
2321 struct cdbdata *cdbfr;
2324 long highest_msg = 0L;
2327 struct CtdlMessage *msg = NULL;
2329 long *msgs_to_be_merged = NULL;
2330 int num_msgs_to_be_merged = 0;
2333 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2334 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2337 strcpy(hold_rm, CC->room.QRname);
2340 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2341 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2342 if (num_newmsgs > 1) supplied_msg = NULL;
2344 /* Now the regular stuff */
2345 if (CtdlGetRoomLock(&CC->room,
2346 ((roomname != NULL) ? roomname : CC->room.QRname) )
2348 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2349 return(ERROR + ROOM_NOT_FOUND);
2353 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2354 num_msgs_to_be_merged = 0;
2357 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2358 if (cdbfr == NULL) {
2362 msglist = (long *) cdbfr->ptr;
2363 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2364 num_msgs = cdbfr->len / sizeof(long);
2369 /* Create a list of msgid's which were supplied by the caller, but do
2370 * not already exist in the target room. It is absolutely taboo to
2371 * have more than one reference to the same message in a room.
2373 for (i=0; i<num_newmsgs; ++i) {
2375 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2376 if (msglist[j] == newmsgidlist[i]) {
2381 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2385 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2388 * Now merge the new messages
2390 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2391 if (msglist == NULL) {
2392 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2393 free(msgs_to_be_merged);
2394 return (ERROR + INTERNAL_ERROR);
2396 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2397 num_msgs += num_msgs_to_be_merged;
2399 /* Sort the message list, so all the msgid's are in order */
2400 num_msgs = sort_msglist(msglist, num_msgs);
2402 /* Determine the highest message number */
2403 highest_msg = msglist[num_msgs - 1];
2405 /* Write it back to disk. */
2406 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2407 msglist, (int)(num_msgs * sizeof(long)));
2409 /* Free up the memory we used. */
2412 /* Update the highest-message pointer and unlock the room. */
2413 CC->room.QRhighest = highest_msg;
2414 CtdlPutRoomLock(&CC->room);
2416 /* Perform replication checks if necessary */
2417 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2418 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2420 for (i=0; i<num_msgs_to_be_merged; ++i) {
2421 msgid = msgs_to_be_merged[i];
2423 if (supplied_msg != NULL) {
2427 msg = CtdlFetchMessage(msgid, 0, 1);
2431 ReplicationChecks(msg);
2433 /* If the message has an Exclusive ID, index that... */
2434 if (!CM_IsEmpty(msg, eExclusiveID)) {
2435 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2438 /* Free up the memory we may have allocated */
2439 if (msg != supplied_msg) {
2448 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2451 /* Submit this room for processing by hooks */
2452 PerformRoomHooks(&CC->room);
2454 /* Go back to the room we were in before we wandered here... */
2455 CtdlGetRoom(&CC->room, hold_rm);
2457 /* Bump the reference count for all messages which were merged */
2458 if (!suppress_refcount_adj) {
2459 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2462 /* Free up memory... */
2463 if (msgs_to_be_merged != NULL) {
2464 free(msgs_to_be_merged);
2467 /* Return success. */
2473 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2476 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2477 int do_repl_check, struct CtdlMessage *supplied_msg)
2479 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2486 * Message base operation to save a new message to the message store
2487 * (returns new message number)
2489 * This is the back end for CtdlSubmitMsg() and should not be directly
2490 * called by server-side modules.
2493 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2501 * If the message is big, set its body aside for storage elsewhere
2502 * and we hide the message body from the serializer
2504 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG)
2507 holdM = msg->cm_fields[eMesageText];
2508 msg->cm_fields[eMesageText] = NULL;
2509 holdMLen = msg->cm_lengths[eMesageText];
2510 msg->cm_lengths[eMesageText] = 0;
2513 /* Serialize our data structure for storage in the database */
2514 CtdlSerializeMessage(&smr, msg);
2517 /* put the message body back into the message */
2518 msg->cm_fields[eMesageText] = holdM;
2519 msg->cm_lengths[eMesageText] = holdMLen;
2524 cprintf("%d Unable to serialize message\n",
2525 ERROR + INTERNAL_ERROR);
2528 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2534 /* Write our little bundle of joy into the message base */
2535 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long),
2538 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2542 retval = cdb_store(CDB_BIGMSGS,
2549 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2554 /* Free the memory we used for the serialized message */
2560 long send_message(struct CtdlMessage *msg) {
2566 /* Get a new message number */
2567 newmsgid = get_new_message_number();
2569 /* Generate an ID if we don't have one already */
2570 if (CM_IsEmpty(msg, emessageId)) {
2571 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2572 (long unsigned int) time(NULL),
2573 (long unsigned int) newmsgid,
2574 CtdlGetConfigStr("c_fqdn")
2577 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2580 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2586 /* Return the *local* message ID to the caller
2587 * (even if we're storing an incoming network message)
2595 * Serialize a struct CtdlMessage into the format used on disk and network.
2597 * This function loads up a "struct ser_ret" (defined in server.h) which
2598 * contains the length of the serialized message and a pointer to the
2599 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2601 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2602 struct CtdlMessage *msg) /* unserialized msg */
2608 * Check for valid message format
2610 if (CM_IsValidMsg(msg) == 0) {
2611 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2618 for (i=0; i < NDiskFields; ++i)
2619 if (msg->cm_fields[FieldOrder[i]] != NULL)
2620 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2622 ret->ser = malloc(ret->len);
2623 if (ret->ser == NULL) {
2624 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2631 ret->ser[1] = msg->cm_anon_type;
2632 ret->ser[2] = msg->cm_format_type;
2635 for (i=0; i < NDiskFields; ++i)
2636 if (msg->cm_fields[FieldOrder[i]] != NULL)
2638 ret->ser[wlen++] = (char)FieldOrder[i];
2640 memcpy(&ret->ser[wlen],
2641 msg->cm_fields[FieldOrder[i]],
2642 msg->cm_lengths[FieldOrder[i]] + 1);
2644 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2647 if (ret->len != wlen) {
2648 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2656 * Check to see if any messages already exist in the current room which
2657 * carry the same Exclusive ID as this one. If any are found, delete them.
2659 void ReplicationChecks(struct CtdlMessage *msg) {
2660 long old_msgnum = (-1L);
2662 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2664 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2666 /* No exclusive id? Don't do anything. */
2667 if (msg == NULL) return;
2668 if (CM_IsEmpty(msg, eExclusiveID)) return;
2670 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2671 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2673 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2674 if (old_msgnum > 0L) {
2675 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2676 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2683 * Save a message to disk and submit it into the delivery system.
2685 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2686 recptypes *recps, /* recipients (if mail) */
2687 const char *force, /* force a particular room? */
2688 int flags /* should the message be exported clean? */
2691 char hold_rm[ROOMNAMELEN];
2692 char actual_rm[ROOMNAMELEN];
2693 char force_room[ROOMNAMELEN];
2694 char content_type[SIZ]; /* We have to learn this */
2695 char recipient[SIZ];
2696 char bounce_to[1024];
2699 const char *mptr = NULL;
2700 struct ctdluser userbuf;
2702 struct MetaData smi;
2703 char *collected_addresses = NULL;
2704 struct addresses_to_be_filed *aptr = NULL;
2705 StrBuf *saved_rfc822_version = NULL;
2706 int qualified_for_journaling = 0;
2708 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2709 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2711 /* If this message has no timestamp, we take the liberty of
2712 * giving it one, right now.
2714 if (CM_IsEmpty(msg, eTimestamp)) {
2715 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2718 /* If this message has no path, we generate one.
2720 if (CM_IsEmpty(msg, eMessagePath)) {
2721 if (!CM_IsEmpty(msg, eAuthor)) {
2722 CM_CopyField(msg, eMessagePath, eAuthor);
2723 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2724 if (isspace(msg->cm_fields[eMessagePath][a])) {
2725 msg->cm_fields[eMessagePath][a] = ' ';
2730 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2734 if (force == NULL) {
2735 force_room[0] = '\0';
2738 strcpy(force_room, force);
2741 /* Learn about what's inside, because it's what's inside that counts */
2742 if (CM_IsEmpty(msg, eMesageText)) {
2743 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2747 switch (msg->cm_format_type) {
2749 strcpy(content_type, "text/x-citadel-variformat");
2752 strcpy(content_type, "text/plain");
2755 strcpy(content_type, "text/plain");
2756 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2759 safestrncpy(content_type, &mptr[13], sizeof content_type);
2760 striplt(content_type);
2761 aptr = content_type;
2762 while (!IsEmptyStr(aptr)) {
2774 /* Goto the correct room */
2775 room = (recps) ? CC->room.QRname : SENTITEMS;
2776 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2777 strcpy(hold_rm, CC->room.QRname);
2778 strcpy(actual_rm, CC->room.QRname);
2779 if (recps != NULL) {
2780 strcpy(actual_rm, SENTITEMS);
2783 /* If the user is a twit, move to the twit room for posting */
2785 if (CC->user.axlevel == AxProbU) {
2786 strcpy(hold_rm, actual_rm);
2787 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2788 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2792 /* ...or if this message is destined for Aide> then go there. */
2793 if (!IsEmptyStr(force_room)) {
2794 strcpy(actual_rm, force_room);
2797 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2798 if (strcasecmp(actual_rm, CC->room.QRname)) {
2799 /* CtdlGetRoom(&CC->room, actual_rm); */
2800 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2804 * If this message has no O (room) field, generate one.
2806 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2807 CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
2810 /* Perform "before save" hooks (aborting if any return nonzero) */
2811 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2812 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2815 * If this message has an Exclusive ID, and the room is replication
2816 * checking enabled, then do replication checks.
2818 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2819 ReplicationChecks(msg);
2822 /* Save it to disk */
2823 syslog(LOG_DEBUG, "msgbase: saving to disk");
2824 newmsgid = send_message(msg);
2825 if (newmsgid <= 0L) return(-5);
2827 /* Write a supplemental message info record. This doesn't have to
2828 * be a critical section because nobody else knows about this message
2831 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2832 memset(&smi, 0, sizeof(struct MetaData));
2833 smi.meta_msgnum = newmsgid;
2834 smi.meta_refcount = 0;
2835 safestrncpy(smi.meta_content_type, content_type,
2836 sizeof smi.meta_content_type);
2839 * Measure how big this message will be when rendered as RFC822.
2840 * We do this for two reasons:
2841 * 1. We need the RFC822 length for the new metadata record, so the
2842 * POP and IMAP services don't have to calculate message lengths
2843 * while the user is waiting (multiplied by potentially hundreds
2844 * or thousands of messages).
2845 * 2. If journaling is enabled, we will need an RFC822 version of the
2846 * message to attach to the journalized copy.
2848 if (CC->redirect_buffer != NULL) {
2849 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2852 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2853 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2854 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2855 saved_rfc822_version = CC->redirect_buffer;
2856 CC->redirect_buffer = NULL;
2860 /* Now figure out where to store the pointers */
2861 syslog(LOG_DEBUG, "msgbase: storing pointers");
2863 /* If this is being done by the networker delivering a private
2864 * message, we want to BYPASS saving the sender's copy (because there
2865 * is no local sender; it would otherwise go to the Trashcan).
2867 if ((!CC->internal_pgm) || (recps == NULL)) {
2868 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2869 syslog(LOG_ERR, "msgbase: ERROR saving message pointer!");
2870 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2874 /* For internet mail, drop a copy in the outbound queue room */
2875 if ((recps != NULL) && (recps->num_internet > 0)) {
2876 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2879 /* If other rooms are specified, drop them there too. */
2880 if ((recps != NULL) && (recps->num_room > 0))
2881 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2882 extract_token(recipient, recps->recp_room, i,
2883 '|', sizeof recipient);
2884 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2885 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2888 /* Bump this user's messages posted counter. */
2889 syslog(LOG_DEBUG, "msgbase: updating user");
2890 CtdlLockGetCurrentUser();
2891 CC->user.posted = CC->user.posted + 1;
2892 CtdlPutCurrentUserLock();
2894 /* Decide where bounces need to be delivered */
2895 if ((recps != NULL) && (recps->bounce_to == NULL))
2897 if (CC->logged_in) {
2898 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CC->user.fullname, CtdlGetConfigStr("c_nodename"));
2901 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
2903 recps->bounce_to = bounce_to;
2906 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2909 /* If this is private, local mail, make a copy in the
2910 * recipient's mailbox and bump the reference count.
2912 if ((recps != NULL) && (recps->num_local > 0))
2917 pch = recps->recp_local;
2918 recps->recp_local = recipient;
2919 ntokens = num_tokens(pch, '|');
2920 for (i=0; i<ntokens; ++i)
2922 extract_token(recipient, pch, i, '|', sizeof recipient);
2923 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2924 if (CtdlGetUser(&userbuf, recipient) == 0) {
2925 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2926 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2927 CtdlBumpNewMailCounter(userbuf.usernum);
2928 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2931 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2932 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2935 recps->recp_local = pch;
2938 /* Perform "after save" hooks */
2939 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2941 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2942 CM_FlushField(msg, eVltMsgNum);
2944 /* Go back to the room we started from */
2945 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2946 if (strcasecmp(hold_rm, CC->room.QRname))
2947 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2950 * Any addresses to harvest for someone's address book?
2952 if ( (CC->logged_in) && (recps != NULL) ) {
2953 collected_addresses = harvest_collected_addresses(msg);
2956 if (collected_addresses != NULL) {
2957 aptr = (struct addresses_to_be_filed *)
2958 malloc(sizeof(struct addresses_to_be_filed));
2959 CtdlMailboxName(actual_rm, sizeof actual_rm,
2960 &CC->user, USERCONTACTSROOM);
2961 aptr->roomname = strdup(actual_rm);
2962 aptr->collected_addresses = collected_addresses;
2963 begin_critical_section(S_ATBF);
2966 end_critical_section(S_ATBF);
2970 * Determine whether this message qualifies for journaling.
2972 if (!CM_IsEmpty(msg, eJournal)) {
2973 qualified_for_journaling = 0;
2976 if (recps == NULL) {
2977 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2979 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2980 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2983 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2988 * Do we have to perform journaling? If so, hand off the saved
2989 * RFC822 version will be handed off to the journaler for background
2990 * submit. Otherwise, we have to free the memory ourselves.
2992 if (saved_rfc822_version != NULL) {
2993 if (qualified_for_journaling) {
2994 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2997 FreeStrBuf(&saved_rfc822_version);
3001 if ((recps != NULL) && (recps->bounce_to == bounce_to))
3002 recps->bounce_to = NULL;
3010 * Convenience function for generating small administrative messages.
3012 long quickie_message(const char *from,
3013 const char *fromaddr,
3018 const char *subject)
3020 struct CtdlMessage *msg;
3021 recptypes *recp = NULL;
3023 msg = malloc(sizeof(struct CtdlMessage));
3024 memset(msg, 0, sizeof(struct CtdlMessage));
3025 msg->cm_magic = CTDLMESSAGE_MAGIC;
3026 msg->cm_anon_type = MES_NORMAL;
3027 msg->cm_format_type = format_type;
3029 if (!IsEmptyStr(from)) {
3030 CM_SetField(msg, eAuthor, from, strlen(from));
3032 else if (!IsEmptyStr(fromaddr)) {
3034 CM_SetField(msg, eAuthor, fromaddr, strlen(fromaddr));
3035 pAt = strchr(msg->cm_fields[eAuthor], '@');
3037 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3041 msg->cm_fields[eAuthor] = strdup("Citadel");
3044 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, strlen(fromaddr));
3045 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, strlen(room));
3046 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
3047 if (!IsEmptyStr(to)) {
3048 CM_SetField(msg, eRecipient, to, strlen(to));
3049 recp = validate_recipients(to, NULL, 0);
3051 if (!IsEmptyStr(subject)) {
3052 CM_SetField(msg, eMsgSubject, subject, strlen(subject));
3054 if (!IsEmptyStr(text)) {
3055 CM_SetField(msg, eMesageText, text, strlen(text));
3058 long msgnum = CtdlSubmitMsg(msg, recp, room, 0);
3060 if (recp != NULL) free_recipients(recp);
3066 * Back end function used by CtdlMakeMessage() and similar functions
3068 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3070 size_t maxlen, /* maximum message length */
3071 StrBuf *exist, /* if non-null, append to it;
3072 exist is ALWAYS freed */
3073 int crlf /* CRLF newlines instead of LF */
3082 LineBuf = NewStrBufPlain(NULL, SIZ);
3083 if (exist == NULL) {
3084 Message = NewStrBufPlain(NULL, 4 * SIZ);
3087 Message = NewStrBufDup(exist);
3090 /* Do we need to change leading ".." to "." for SMTP escaping? */
3091 if ((tlen == 1) && (*terminator == '.')) {
3095 /* read in the lines of message text one by one */
3097 if (CtdlClientGetLine(LineBuf) < 0) {
3100 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3103 if ( (!flushing) && (!finished) ) {
3105 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3108 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3111 /* Unescape SMTP-style input of two dots at the beginning of the line */
3113 (StrLength(LineBuf) == 2) &&
3114 (!strcmp(ChrPtr(LineBuf), "..")))
3116 StrBufCutLeft(LineBuf, 1);
3119 StrBufAppendBuf(Message, LineBuf, 0);
3122 /* if we've hit the max msg length, flush the rest */
3123 if (StrLength(Message) >= maxlen) flushing = 1;
3125 } while (!finished);
3126 FreeStrBuf(&LineBuf);
3132 * Back end function used by CtdlMakeMessage() and similar functions
3134 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3136 size_t maxlen, /* maximum message length */
3137 StrBuf *exist, /* if non-null, append to it;
3138 exist is ALWAYS freed */
3139 int crlf /* CRLF newlines instead of LF */
3144 Message = CtdlReadMessageBodyBuf(terminator,
3150 if (Message == NULL)
3153 return SmashStrBuf(&Message);
3156 struct CtdlMessage *CtdlMakeMessage(
3157 struct ctdluser *author, /* author's user structure */
3158 char *recipient, /* NULL if it's not mail */
3159 char *recp_cc, /* NULL if it's not mail */
3160 char *room, /* room where it's going */
3161 int type, /* see MES_ types in header file */
3162 int format_type, /* variformat, plain text, MIME... */
3163 char *fake_name, /* who we're masquerading as */
3164 char *my_email, /* which of my email addresses to use (empty is ok) */
3165 char *subject, /* Subject (optional) */
3166 char *supplied_euid, /* ...or NULL if this is irrelevant */
3167 char *preformatted_text, /* ...or NULL to read text from client */
3168 char *references /* Thread references */
3171 return CtdlMakeMessageLen(
3172 author, /* author's user structure */
3173 recipient, /* NULL if it's not mail */
3174 (recipient)?strlen(recipient) : 0,
3175 recp_cc, /* NULL if it's not mail */
3176 (recp_cc)?strlen(recp_cc): 0,
3177 room, /* room where it's going */
3178 (room)?strlen(room): 0,
3179 type, /* see MES_ types in header file */
3180 format_type, /* variformat, plain text, MIME... */
3181 fake_name, /* who we're masquerading as */
3182 (fake_name)?strlen(fake_name): 0,
3183 my_email, /* which of my email addresses to use (empty is ok) */
3184 (my_email)?strlen(my_email): 0,
3185 subject, /* Subject (optional) */
3186 (subject)?strlen(subject): 0,
3187 supplied_euid, /* ...or NULL if this is irrelevant */
3188 (supplied_euid)?strlen(supplied_euid):0,
3189 preformatted_text, /* ...or NULL to read text from client */
3190 (preformatted_text)?strlen(preformatted_text) : 0,
3191 references, /* Thread references */
3192 (references)?strlen(references):0);
3197 * Build a binary message to be saved on disk.
3198 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3199 * will become part of the message. This means you are no longer
3200 * responsible for managing that memory -- it will be freed along with
3201 * the rest of the fields when CM_Free() is called.)
3204 struct CtdlMessage *CtdlMakeMessageLen(
3205 struct ctdluser *author, /* author's user structure */
3206 char *recipient, /* NULL if it's not mail */
3208 char *recp_cc, /* NULL if it's not mail */
3210 char *room, /* room where it's going */
3212 int type, /* see MES_ types in header file */
3213 int format_type, /* variformat, plain text, MIME... */
3214 char *fake_name, /* who we're masquerading as */
3216 char *my_email, /* which of my email addresses to use (empty is ok) */
3218 char *subject, /* Subject (optional) */
3220 char *supplied_euid, /* ...or NULL if this is irrelevant */
3222 char *preformatted_text, /* ...or NULL to read text from client */
3224 char *references, /* Thread references */
3228 /* Don't confuse the poor folks if it's not routed mail. * /
3229 char dest_node[256] = "";*/
3232 struct CtdlMessage *msg;
3234 StrBuf *FakeEncAuthor = NULL;
3236 msg = malloc(sizeof(struct CtdlMessage));
3237 memset(msg, 0, sizeof(struct CtdlMessage));
3238 msg->cm_magic = CTDLMESSAGE_MAGIC;
3239 msg->cm_anon_type = type;
3240 msg->cm_format_type = format_type;
3242 if (recipient != NULL) rcplen = striplt(recipient);
3243 if (recp_cc != NULL) cclen = striplt(recp_cc);
3245 /* Path or Return-Path */
3247 CM_SetField(msg, eMessagePath, my_email, myelen);
3249 else if (!IsEmptyStr(author->fullname)) {
3250 CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
3252 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3254 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3255 CM_SetField(msg, eTimestamp, buf, blen);
3258 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3261 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3263 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3264 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3265 FreeStrBuf(&FakeAuthor);
3267 if (!!IsEmptyStr(CC->room.QRname)) {
3268 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3269 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], strlen(&CC->room.QRname[11]));
3272 CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
3276 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
3277 CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
3280 CM_SetField(msg, eRecipient, recipient, rcplen);
3283 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3287 CM_SetField(msg, erFc822Addr, my_email, myelen);
3289 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3290 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
3293 if (subject != NULL) {
3295 length = striplt(subject);
3301 while ((subject[i] != '\0') &&
3302 (IsAscii = isascii(subject[i]) != 0 ))
3305 CM_SetField(msg, eMsgSubject, subject, subjlen);
3306 else /* ok, we've got utf8 in the string. */
3309 rfc2047Subj = rfc2047encode(subject, length);
3310 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3317 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3321 CM_SetField(msg, eWeferences, references, reflen);
3324 if (preformatted_text != NULL) {
3325 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3329 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3330 if (MsgBody != NULL) {
3331 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3342 * API function to delete messages which match a set of criteria
3343 * (returns the actual number of messages deleted)
3345 int CtdlDeleteMessages(const char *room_name, /* which room */
3346 long *dmsgnums, /* array of msg numbers to be deleted */
3347 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3348 char *content_type /* or "" for any. regular expressions expected. */
3351 struct ctdlroom qrbuf;
3352 struct cdbdata *cdbfr;
3353 long *msglist = NULL;
3354 long *dellist = NULL;
3357 int num_deleted = 0;
3359 struct MetaData smi;
3362 int need_to_free_re = 0;
3364 if (content_type) if (!IsEmptyStr(content_type)) {
3365 regcomp(&re, content_type, 0);
3366 need_to_free_re = 1;
3368 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3370 /* get room record, obtaining a lock... */
3371 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3372 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3373 if (need_to_free_re) regfree(&re);
3374 return(0); /* room not found */
3376 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3378 if (cdbfr != NULL) {
3379 dellist = malloc(cdbfr->len);
3380 msglist = (long *) cdbfr->ptr;
3381 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3382 num_msgs = cdbfr->len / sizeof(long);
3386 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3387 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3388 int have_more_del = 1;
3390 num_msgs = sort_msglist(msglist, num_msgs);
3391 if (num_dmsgnums > 1)
3392 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3395 StrBuf *dbg = NewStrBuf();
3396 for (i = 0; i < num_dmsgnums; i++)
3397 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3398 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3403 while ((i < num_msgs) && (have_more_del)) {
3406 /* Set/clear a bit for each criterion */
3408 /* 0 messages in the list or a null list means that we are
3409 * interested in deleting any messages which meet the other criteria.
3412 delete_this |= 0x01;
3415 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3420 if (msglist[i] == dmsgnums[j]) {
3421 delete_this |= 0x01;
3424 have_more_del = (j < num_dmsgnums);
3427 if (have_contenttype) {
3428 GetMetaData(&smi, msglist[i]);
3429 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3430 delete_this |= 0x02;
3433 delete_this |= 0x02;
3436 /* Delete message only if all bits are set */
3437 if (delete_this == 0x03) {
3438 dellist[num_deleted++] = msglist[i];
3445 StrBuf *dbg = NewStrBuf();
3446 for (i = 0; i < num_deleted; i++)
3447 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3448 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3452 num_msgs = sort_msglist(msglist, num_msgs);
3453 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3454 msglist, (int)(num_msgs * sizeof(long)));
3457 qrbuf.QRhighest = msglist[num_msgs - 1];
3459 qrbuf.QRhighest = 0;
3461 CtdlPutRoomLock(&qrbuf);
3463 /* Go through the messages we pulled out of the index, and decrement
3464 * their reference counts by 1. If this is the only room the message
3465 * was in, the reference count will reach zero and the message will
3466 * automatically be deleted from the database. We do this in a
3467 * separate pass because there might be plug-in hooks getting called,
3468 * and we don't want that happening during an S_ROOMS critical
3472 for (i=0; i<num_deleted; ++i) {
3473 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3475 AdjRefCountList(dellist, num_deleted, -1);
3477 /* Now free the memory we used, and go away. */
3478 if (msglist != NULL) free(msglist);
3479 if (dellist != NULL) free(dellist);
3480 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3481 if (need_to_free_re) regfree(&re);
3482 return (num_deleted);
3487 * GetMetaData() - Get the supplementary record for a message
3489 void GetMetaData(struct MetaData *smibuf, long msgnum)
3491 struct cdbdata *cdbsmi;
3494 memset(smibuf, 0, sizeof(struct MetaData));
3495 smibuf->meta_msgnum = msgnum;
3496 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3498 /* Use the negative of the message number for its supp record index */
3499 TheIndex = (0L - msgnum);
3501 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3502 if (cdbsmi == NULL) {
3503 return; /* record not found; leave it alone */
3505 memcpy(smibuf, cdbsmi->ptr,
3506 ((cdbsmi->len > sizeof(struct MetaData)) ?
3507 sizeof(struct MetaData) : cdbsmi->len)
3515 * PutMetaData() - (re)write supplementary record for a message
3517 void PutMetaData(struct MetaData *smibuf)
3521 /* Use the negative of the message number for the metadata db index */
3522 TheIndex = (0L - smibuf->meta_msgnum);
3524 cdb_store(CDB_MSGMAIN,
3525 &TheIndex, (int)sizeof(long),
3526 smibuf, (int)sizeof(struct MetaData)
3532 * Convenience function to process a big block of AdjRefCount() operations
3534 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3538 for (i = 0; i < nmsg; i++) {
3539 AdjRefCount(msgnum[i], incr);
3545 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3547 void AdjRefCount(long msgnum, int incr)
3549 struct MetaData smi;
3552 /* This is a *tight* critical section; please keep it that way, as
3553 * it may get called while nested in other critical sections.
3554 * Complicating this any further will surely cause deadlock!
3556 begin_critical_section(S_SUPPMSGMAIN);
3557 GetMetaData(&smi, msgnum);
3558 smi.meta_refcount += incr;
3560 end_critical_section(S_SUPPMSGMAIN);
3561 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3563 /* If the reference count is now zero, delete both the message and its metadata record.
3565 if (smi.meta_refcount == 0) {
3566 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3568 /* Call delete hooks with NULL room to show it has gone altogether */
3569 PerformDeleteHooks(NULL, msgnum);
3571 /* Remove from message base */
3573 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3574 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3576 /* Remove metadata record */
3577 delnum = (0L - msgnum);
3578 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3584 * Write a generic object to this room
3586 * Note: this could be much more efficient. Right now we use two temporary
3587 * files, and still pull the message into memory as with all others.
3589 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3590 char *content_type, /* MIME type of this object */
3591 char *raw_message, /* Data to be written */
3592 off_t raw_length, /* Size of raw_message */
3593 struct ctdluser *is_mailbox, /* Mailbox room? */
3594 int is_binary, /* Is encoding necessary? */
3595 int is_unique, /* Del others of this type? */
3596 unsigned int flags /* Internal save flags */
3599 struct ctdlroom qrbuf;
3600 char roomname[ROOMNAMELEN];
3601 struct CtdlMessage *msg;
3602 StrBuf *encoded_message = NULL;
3604 if (is_mailbox != NULL) {
3605 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3608 safestrncpy(roomname, req_room, sizeof(roomname));
3611 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3614 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3617 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3620 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3621 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3622 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3625 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3628 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3632 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3635 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3638 syslog(LOG_DEBUG, "msgbase: allocating");
3639 msg = malloc(sizeof(struct CtdlMessage));
3640 memset(msg, 0, sizeof(struct CtdlMessage));
3641 msg->cm_magic = CTDLMESSAGE_MAGIC;
3642 msg->cm_anon_type = MES_NORMAL;
3643 msg->cm_format_type = 4;
3644 CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
3645 CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
3646 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
3647 CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
3648 msg->cm_flags = flags;
3650 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3652 /* Create the requested room if we have to. */
3653 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3654 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3656 /* If the caller specified this object as unique, delete all
3657 * other objects of this type that are currently in the room.
3660 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3661 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3664 /* Now write the data */
3665 CtdlSubmitMsg(msg, NULL, roomname, 0);
3670 /************************************************************************/
3671 /* MODULE INITIALIZATION */
3672 /************************************************************************/
3674 CTDL_MODULE_INIT(msgbase)
3677 FillMsgKeyLookupTable();
3680 /* return our module id for the log */