2 * Implements the message store.
4 * Copyright (c) 1987-2021 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #include <libcitadel.h>
22 #include "ctdl_module.h"
23 #include "citserver.h"
26 #include "clientsocket.h"
30 #include "internet_addressing.h"
31 #include "euidindex.h"
33 #include "journaling.h"
35 struct addresses_to_be_filed *atbf = NULL;
38 * These are the four-character field headers we use when outputting
39 * messages in Citadel format (as opposed to RFC822 format).
42 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
43 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
44 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
45 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
46 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
47 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
48 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
49 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
51 "from", // A -> eAuthor
52 NULL, // B -> eBig_message
53 NULL, // C (formerly used as eRemoteRoom)
54 NULL, // D (formerly used as eDestination)
55 "exti", // E -> eXclusivID
56 "rfca", // F -> erFc822Addr
58 "hnod", // H (formerly used as eHumanNode)
59 "msgn", // I -> emessageId
60 "jrnl", // J -> eJournal
61 "rep2", // K -> eReplyTo
62 "list", // L -> eListID
63 "text", // M -> eMesageText
64 NULL, // N (formerly used as eNodename)
65 "room", // O -> eOriginalRoom
66 "path", // P -> eMessagePath
68 "rcpt", // R -> eRecipient
69 NULL, // S (formerly used as eSpecialField)
70 "time", // T -> eTimestamp
71 "subj", // U -> eMsgSubject
72 "nvto", // V -> eenVelopeTo
73 "wefw", // W -> eWeferences
75 "cccc", // Y -> eCarbonCopY
80 HashList *msgKeyLookup = NULL;
82 int GetFieldFromMnemonic(eMsgField *f, const char* c)
85 if (GetHash(msgKeyLookup, c, 4, &v)) {
92 void FillMsgKeyLookupTable(void)
96 msgKeyLookup = NewHash (1, FourHash);
98 for (i=0; i < 91; i++) {
99 if (msgkeys[i] != NULL) {
100 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
106 eMsgField FieldOrder[] = {
107 /* Important fields */
115 /* Semi-important fields */
120 /* G is not used yet */
123 /* Q is not used yet */
125 /* X is not used yet */
126 /* Z is not used yet */
133 /* Message text (MUST be last) */
135 /* Not saved to disk:
140 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
143 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
144 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
148 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
149 if (Msg->cm_fields[which] != NULL) {
150 free (Msg->cm_fields[which]);
152 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
153 length = strlen(buf);
155 Msg->cm_fields[which] = malloc(length + 1);
156 memcpy(Msg->cm_fields[which], buf, length);
157 Msg->cm_fields[which][length] = '\0';
158 Msg->cm_lengths[which] = length;
162 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
165 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
166 CM_SetField(Msg, which, buf, len);
170 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
171 if (Msg->cm_fields[WhichToCut] == NULL)
174 if (Msg->cm_lengths[WhichToCut] > maxlen)
176 Msg->cm_fields[WhichToCut][maxlen] = '\0';
177 Msg->cm_lengths[WhichToCut] = maxlen;
182 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
183 if (Msg->cm_fields[which] != NULL)
184 free (Msg->cm_fields[which]);
185 Msg->cm_fields[which] = NULL;
186 Msg->cm_lengths[which] = 0;
190 void CM_Flush(struct CtdlMessage *Msg) {
193 if (CM_IsValidMsg(Msg) == 0) {
197 for (i = 0; i < 256; ++i) {
198 CM_FlushField(Msg, i);
203 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
205 if (Msg->cm_fields[WhichToPutTo] != NULL) {
206 free (Msg->cm_fields[WhichToPutTo]);
209 if (Msg->cm_fields[WhichtToCopy] != NULL) {
210 len = Msg->cm_lengths[WhichtToCopy];
211 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
212 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
213 Msg->cm_fields[WhichToPutTo][len] = '\0';
214 Msg->cm_lengths[WhichToPutTo] = len;
217 Msg->cm_fields[WhichToPutTo] = NULL;
218 Msg->cm_lengths[WhichToPutTo] = 0;
223 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
224 if (Msg->cm_fields[which] != NULL) {
229 oldmsgsize = Msg->cm_lengths[which] + 1;
230 newmsgsize = length + oldmsgsize;
232 new = malloc(newmsgsize);
233 memcpy(new, buf, length);
234 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
235 free(Msg->cm_fields[which]);
236 Msg->cm_fields[which] = new;
237 Msg->cm_lengths[which] = newmsgsize - 1;
240 Msg->cm_fields[which] = malloc(length + 1);
241 memcpy(Msg->cm_fields[which], buf, length);
242 Msg->cm_fields[which][length] = '\0';
243 Msg->cm_lengths[which] = length;
248 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
249 if (Msg->cm_fields[which] != NULL) {
250 free (Msg->cm_fields[which]);
253 Msg->cm_fields[which] = *buf;
255 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
256 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
259 Msg->cm_lengths[which] = length;
264 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
265 if (Msg->cm_fields[which] != NULL) {
266 free (Msg->cm_fields[which]);
269 Msg->cm_lengths[which] = StrLength(*buf);
270 Msg->cm_fields[which] = SmashStrBuf(buf);
274 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
275 if (Msg->cm_fields[which] != NULL) {
276 *retlen = Msg->cm_lengths[which];
277 *ret = Msg->cm_fields[which];
278 Msg->cm_fields[which] = NULL;
279 Msg->cm_lengths[which] = 0;
289 * Returns 1 if the supplied pointer points to a valid Citadel message.
290 * If the pointer is NULL or the magic number check fails, returns 0.
292 int CM_IsValidMsg(struct CtdlMessage *msg) {
296 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
297 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
304 void CM_FreeContents(struct CtdlMessage *msg) {
307 for (i = 0; i < 256; ++i)
308 if (msg->cm_fields[i] != NULL) {
309 free(msg->cm_fields[i]);
310 msg->cm_lengths[i] = 0;
313 msg->cm_magic = 0; /* just in case */
318 * 'Destructor' for struct CtdlMessage
320 void CM_Free(struct CtdlMessage *msg) {
321 if (CM_IsValidMsg(msg) == 0) {
322 if (msg != NULL) free (msg);
325 CM_FreeContents(msg);
330 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
332 len = OrgMsg->cm_lengths[i];
333 NewMsg->cm_fields[i] = malloc(len + 1);
334 if (NewMsg->cm_fields[i] == NULL) {
337 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
338 NewMsg->cm_fields[i][len] = '\0';
339 NewMsg->cm_lengths[i] = len;
344 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
346 struct CtdlMessage *NewMsg;
348 if (CM_IsValidMsg(OrgMsg) == 0) {
351 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
352 if (NewMsg == NULL) {
356 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
358 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
360 for (i = 0; i < 256; ++i) {
361 if (OrgMsg->cm_fields[i] != NULL) {
362 if (!CM_DupField(i, OrgMsg, NewMsg)) {
373 /* Determine if a given message matches the fields in a message template.
374 * Return 0 for a successful match.
376 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
379 /* If there aren't any fields in the template, all messages will
382 if (template == NULL) return(0);
384 /* Null messages are bogus. */
385 if (msg == NULL) return(1);
387 for (i='A'; i<='Z'; ++i) {
388 if (template->cm_fields[i] != NULL) {
389 if (msg->cm_fields[i] == NULL) {
390 /* Considered equal if temmplate is empty string */
391 if (IsEmptyStr(template->cm_fields[i])) continue;
394 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
395 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
400 /* All compares succeeded: we have a match! */
406 * Retrieve the "seen" message list for the current room.
408 void CtdlGetSeen(char *buf, int which_set) {
411 /* Learn about the user and room in question */
412 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
414 if (which_set == ctdlsetseen_seen) {
415 safestrncpy(buf, vbuf.v_seen, SIZ);
417 if (which_set == ctdlsetseen_answered) {
418 safestrncpy(buf, vbuf.v_answered, SIZ);
424 * Manipulate the "seen msgs" string (or other message set strings)
426 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
427 int target_setting, int which_set,
428 struct ctdluser *which_user, struct ctdlroom *which_room) {
429 struct cdbdata *cdbfr;
443 char *is_set; /* actually an array of booleans */
445 /* Don't bother doing *anything* if we were passed a list of zero messages */
446 if (num_target_msgnums < 1) {
450 /* If no room was specified, we go with the current room. */
452 which_room = &CC->room;
455 /* If no user was specified, we go with the current user. */
457 which_user = &CC->user;
460 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
461 num_target_msgnums, target_msgnums[0],
462 (target_setting ? "SET" : "CLEAR"),
466 /* Learn about the user and room in question */
467 CtdlGetRelationship(&vbuf, which_user, which_room);
469 /* Load the message list */
470 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
472 msglist = (long *) cdbfr->ptr;
473 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
474 num_msgs = cdbfr->len / sizeof(long);
478 return; /* No messages at all? No further action. */
481 is_set = malloc(num_msgs * sizeof(char));
482 memset(is_set, 0, (num_msgs * sizeof(char)) );
484 /* Decide which message set we're manipulating */
486 case ctdlsetseen_seen:
487 vset = NewStrBufPlain(vbuf.v_seen, -1);
489 case ctdlsetseen_answered:
490 vset = NewStrBufPlain(vbuf.v_answered, -1);
497 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
498 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
499 for (i=0; i<num_msgs; ++i) {
500 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
502 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
503 for (k=0; k<num_target_msgnums; ++k) {
504 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
508 /* Translate the existing sequence set into an array of booleans */
509 setstr = NewStrBuf();
513 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
515 StrBufExtract_token(lostr, setstr, 0, ':');
516 if (StrBufNum_tokens(setstr, ':') >= 2) {
517 StrBufExtract_token(histr, setstr, 1, ':');
521 StrBufAppendBuf(histr, lostr, 0);
524 if (!strcmp(ChrPtr(histr), "*")) {
531 for (i = 0; i < num_msgs; ++i) {
532 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
541 /* Now translate the array of booleans back into a sequence set */
547 for (i=0; i<num_msgs; ++i) {
551 for (k=0; k<num_target_msgnums; ++k) {
552 if (msglist[i] == target_msgnums[k]) {
553 is_seen = target_setting;
557 if ((was_seen == 0) && (is_seen == 1)) {
560 else if ((was_seen == 1) && (is_seen == 0)) {
563 if (StrLength(vset) > 0) {
564 StrBufAppendBufPlain(vset, HKEY(","), 0);
567 StrBufAppendPrintf(vset, "%ld", hi);
570 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
574 if ((is_seen) && (i == num_msgs - 1)) {
575 if (StrLength(vset) > 0) {
576 StrBufAppendBufPlain(vset, HKEY(","), 0);
578 if ((i==0) || (was_seen == 0)) {
579 StrBufAppendPrintf(vset, "%ld", msglist[i]);
582 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
590 * We will have to stuff this string back into a 4096 byte buffer, so if it's
591 * larger than that now, truncate it by removing tokens from the beginning.
592 * The limit of 100 iterations is there to prevent an infinite loop in case
593 * something unexpected happens.
595 int number_of_truncations = 0;
596 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
597 StrBufRemove_token(vset, 0, ',');
598 ++number_of_truncations;
602 * If we're truncating the sequence set of messages marked with the 'seen' flag,
603 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
604 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
606 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
608 first_tok = NewStrBuf();
609 StrBufExtract_token(first_tok, vset, 0, ',');
610 StrBufRemove_token(vset, 0, ',');
612 if (StrBufNum_tokens(first_tok, ':') > 1) {
613 StrBufRemove_token(first_tok, 0, ':');
617 new_set = NewStrBuf();
618 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
619 StrBufAppendBuf(new_set, first_tok, 0);
620 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
621 StrBufAppendBuf(new_set, vset, 0);
624 FreeStrBuf(&first_tok);
628 /* Decide which message set we're manipulating */
630 case ctdlsetseen_seen:
631 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
633 case ctdlsetseen_answered:
634 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
640 CtdlSetRelationship(&vbuf, which_user, which_room);
646 * API function to perform an operation for each qualifying message in the
647 * current room. (Returns the number of messages processed.)
649 int CtdlForEachMessage(int mode, long ref, char *search_string,
651 struct CtdlMessage *compare,
652 ForEachMsgCallback CallBack,
657 struct cdbdata *cdbfr;
658 long *msglist = NULL;
660 int num_processed = 0;
663 struct CtdlMessage *msg = NULL;
666 int printed_lastold = 0;
667 int num_search_msgs = 0;
668 long *search_msgs = NULL;
670 int need_to_free_re = 0;
673 if ((content_type) && (!IsEmptyStr(content_type))) {
674 regcomp(&re, content_type, 0);
678 /* Learn about the user and room in question */
679 if (server_shutting_down) {
680 if (need_to_free_re) regfree(&re);
683 CtdlGetUser(&CC->user, CC->curr_user);
685 if (server_shutting_down) {
686 if (need_to_free_re) regfree(&re);
689 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
691 if (server_shutting_down) {
692 if (need_to_free_re) regfree(&re);
696 /* Load the message list */
697 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
699 if (need_to_free_re) regfree(&re);
700 return 0; /* No messages at all? No further action. */
703 msglist = (long *) cdbfr->ptr;
704 num_msgs = cdbfr->len / sizeof(long);
706 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
707 cdb_free(cdbfr); /* we own this memory now */
710 * Now begin the traversal.
712 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
714 /* If the caller is looking for a specific MIME type, filter
715 * out all messages which are not of the type requested.
717 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
719 /* This call to GetMetaData() sits inside this loop
720 * so that we only do the extra database read per msg
721 * if we need to. Doing the extra read all the time
722 * really kills the server. If we ever need to use
723 * metadata for another search criterion, we need to
724 * move the read somewhere else -- but still be smart
725 * enough to only do the read if the caller has
726 * specified something that will need it.
728 if (server_shutting_down) {
729 if (need_to_free_re) regfree(&re);
733 GetMetaData(&smi, msglist[a]);
735 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
736 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
742 num_msgs = sort_msglist(msglist, num_msgs);
744 /* If a template was supplied, filter out the messages which
745 * don't match. (This could induce some delays!)
748 if (compare != NULL) {
749 for (a = 0; a < num_msgs; ++a) {
750 if (server_shutting_down) {
751 if (need_to_free_re) regfree(&re);
755 msg = CtdlFetchMessage(msglist[a], 1);
757 if (CtdlMsgCmp(msg, compare)) {
766 /* If a search string was specified, get a message list from
767 * the full text index and remove messages which aren't on both
771 * Since the lists are sorted and strictly ascending, and the
772 * output list is guaranteed to be shorter than or equal to the
773 * input list, we overwrite the bottom of the input list. This
774 * eliminates the need to memmove big chunks of the list over and
777 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
779 /* Call search module via hook mechanism.
780 * NULL means use any search function available.
781 * otherwise replace with a char * to name of search routine
783 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
785 if (num_search_msgs > 0) {
789 orig_num_msgs = num_msgs;
791 for (i=0; i<orig_num_msgs; ++i) {
792 for (j=0; j<num_search_msgs; ++j) {
793 if (msglist[i] == search_msgs[j]) {
794 msglist[num_msgs++] = msglist[i];
800 num_msgs = 0; /* No messages qualify */
802 if (search_msgs != NULL) free(search_msgs);
804 /* Now that we've purged messages which don't contain the search
805 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
812 * Now iterate through the message list, according to the
813 * criteria supplied by the caller.
816 for (a = 0; a < num_msgs; ++a) {
817 if (server_shutting_down) {
818 if (need_to_free_re) regfree(&re);
820 return num_processed;
822 thismsg = msglist[a];
823 if (mode == MSGS_ALL) {
827 is_seen = is_msg_in_sequence_set(
828 vbuf.v_seen, thismsg);
829 if (is_seen) lastold = thismsg;
835 || ((mode == MSGS_OLD) && (is_seen))
836 || ((mode == MSGS_NEW) && (!is_seen))
837 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
838 || ((mode == MSGS_FIRST) && (a < ref))
839 || ((mode == MSGS_GT) && (thismsg > ref))
840 || ((mode == MSGS_LT) && (thismsg < ref))
841 || ((mode == MSGS_EQ) && (thismsg == ref))
844 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
846 CallBack(lastold, userdata);
852 CallBack(thismsg, userdata);
857 if (need_to_free_re) regfree(&re);
860 * We cache the most recent msglist in order to do security checks later
862 if (CC->client_socket > 0) {
863 if (CC->cached_msglist != NULL) {
864 free(CC->cached_msglist);
866 CC->cached_msglist = msglist;
867 CC->cached_num_msgs = num_msgs;
873 return num_processed;
878 * memfmout() - Citadel text formatter and paginator.
879 * Although the original purpose of this routine was to format
880 * text to the reader's screen width, all we're really using it
881 * for here is to format text out to 80 columns before sending it
882 * to the client. The client software may reformat it again.
885 char *mptr, /* where are we going to get our text from? */
886 const char *nl /* string to terminate lines with */
889 unsigned char ch = 0;
896 while (ch=*(mptr++), ch != 0) {
899 if (client_write(outbuf, len) == -1) {
900 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
904 if (client_write(nl, nllen) == -1) {
905 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
910 else if (ch == '\r') {
911 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
913 else if (isspace(ch)) {
914 if (column > 72) { /* Beyond 72 columns, break on the next space */
915 if (client_write(outbuf, len) == -1) {
916 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
920 if (client_write(nl, nllen) == -1) {
921 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
934 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
935 if (client_write(outbuf, len) == -1) {
936 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
940 if (client_write(nl, nllen) == -1) {
941 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
949 if (client_write(outbuf, len) == -1) {
950 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
953 client_write(nl, nllen);
960 * Callback function for mime parser that simply lists the part
962 void list_this_part(char *name, char *filename, char *partnum, char *disp,
963 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
964 char *cbid, void *cbuserdata)
968 ma = (struct ma_info *)cbuserdata;
969 if (ma->is_ma == 0) {
970 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
984 * Callback function for multipart prefix
986 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
987 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
988 char *cbid, void *cbuserdata)
992 ma = (struct ma_info *)cbuserdata;
993 if (!strcasecmp(cbtype, "multipart/alternative")) {
997 if (ma->is_ma == 0) {
998 cprintf("pref=%s|%s\n", partnum, cbtype);
1004 * Callback function for multipart sufffix
1006 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1007 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1008 char *cbid, void *cbuserdata)
1012 ma = (struct ma_info *)cbuserdata;
1013 if (ma->is_ma == 0) {
1014 cprintf("suff=%s|%s\n", partnum, cbtype);
1016 if (!strcasecmp(cbtype, "multipart/alternative")) {
1023 * Callback function for mime parser that opens a section for downloading
1024 * we use serv_files function here:
1026 extern void OpenCmdResult(char *filename, const char *mime_type);
1027 void mime_download(char *name, char *filename, char *partnum, char *disp,
1028 void *content, char *cbtype, char *cbcharset, size_t length,
1029 char *encoding, char *cbid, void *cbuserdata)
1033 /* Silently go away if there's already a download open. */
1034 if (CC->download_fp != NULL)
1038 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1039 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1041 CC->download_fp = tmpfile();
1042 if (CC->download_fp == NULL) {
1043 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
1044 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
1048 rv = fwrite(content, length, 1, CC->download_fp);
1050 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
1051 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
1052 fclose(CC->download_fp);
1053 CC->download_fp = NULL;
1056 fflush(CC->download_fp);
1057 rewind(CC->download_fp);
1059 OpenCmdResult(filename, cbtype);
1065 * Callback function for mime parser that outputs a section all at once.
1066 * We can specify the desired section by part number *or* content-id.
1068 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1069 void *content, char *cbtype, char *cbcharset, size_t length,
1070 char *encoding, char *cbid, void *cbuserdata)
1072 int *found_it = (int *)cbuserdata;
1075 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1076 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1079 cprintf("%d %d|-1|%s|%s|%s\n",
1086 client_write(content, length);
1091 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1093 struct CtdlMessage *ret = NULL;
1095 const char *upper_bound;
1097 cit_uint8_t field_header;
1101 upper_bound = Buffer + Length;
1106 /* Parse the three bytes that begin EVERY message on disk.
1107 * The first is always 0xFF, the on-disk magic number.
1108 * The second is the anonymous/public type byte.
1109 * The third is the format type byte (vari, fixed, or MIME).
1113 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1116 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1117 memset(ret, 0, sizeof(struct CtdlMessage));
1119 ret->cm_magic = CTDLMESSAGE_MAGIC;
1120 ret->cm_anon_type = *mptr++; /* Anon type byte */
1121 ret->cm_format_type = *mptr++; /* Format type byte */
1124 * The rest is zero or more arbitrary fields. Load them in.
1125 * We're done when we encounter either a zero-length field or
1126 * have just processed the 'M' (message text) field.
1129 field_header = '\0';
1132 /* work around possibly buggy messages: */
1133 while (field_header == '\0') {
1134 if (mptr >= upper_bound) {
1137 field_header = *mptr++;
1139 if (mptr >= upper_bound) {
1142 which = field_header;
1145 CM_SetField(ret, which, mptr, len);
1147 mptr += len + 1; /* advance to next field */
1149 } while ((mptr < upper_bound) && (field_header != 'M'));
1156 * Load a message from disk into memory.
1157 * This is used by CtdlOutputMsg() and other fetch functions.
1159 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1160 * using the CM_Free(); function.
1162 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1164 struct cdbdata *dmsgtext;
1165 struct CtdlMessage *ret = NULL;
1167 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1168 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1169 if (dmsgtext == NULL) {
1170 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Failed!", msgnum, with_body);
1174 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
1175 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1176 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1179 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1187 /* Always make sure there's something in the msg text field. If
1188 * it's NULL, the message text is most likely stored separately,
1189 * so go ahead and fetch that. Failing that, just set a dummy
1190 * body so other code doesn't barf.
1192 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1193 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1194 if (dmsgtext != NULL) {
1195 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1199 if (CM_IsEmpty(ret, eMesageText)) {
1200 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1208 * Pre callback function for multipart/alternative
1210 * NOTE: this differs from the standard behavior for a reason. Normally when
1211 * displaying multipart/alternative you want to show the _last_ usable
1212 * format in the message. Here we show the _first_ one, because it's
1213 * usually text/plain. Since this set of functions is designed for text
1214 * output to non-MIME-aware clients, this is the desired behavior.
1217 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1218 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1219 char *cbid, void *cbuserdata)
1223 ma = (struct ma_info *)cbuserdata;
1224 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1225 if (!strcasecmp(cbtype, "multipart/alternative")) {
1229 if (!strcasecmp(cbtype, "message/rfc822")) {
1236 * Post callback function for multipart/alternative
1238 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1239 void *content, char *cbtype, char *cbcharset, size_t length,
1240 char *encoding, char *cbid, void *cbuserdata)
1244 ma = (struct ma_info *)cbuserdata;
1245 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1246 if (!strcasecmp(cbtype, "multipart/alternative")) {
1250 if (!strcasecmp(cbtype, "message/rfc822")) {
1257 * Inline callback function for mime parser that wants to display text
1259 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1260 void *content, char *cbtype, char *cbcharset, size_t length,
1261 char *encoding, char *cbid, void *cbuserdata)
1268 ma = (struct ma_info *)cbuserdata;
1271 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1272 partnum, filename, cbtype, (long)length
1276 * If we're in the middle of a multipart/alternative scope and
1277 * we've already printed another section, skip this one.
1279 if ( (ma->is_ma) && (ma->did_print) ) {
1280 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1285 if ( (!strcasecmp(cbtype, "text/plain"))
1286 || (IsEmptyStr(cbtype)) ) {
1289 client_write(wptr, length);
1290 if (wptr[length-1] != '\n') {
1297 if (!strcasecmp(cbtype, "text/html")) {
1298 ptr = html_to_ascii(content, length, 80);
1300 client_write(ptr, wlen);
1301 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1308 if (ma->use_fo_hooks) {
1309 if (PerformFixedOutputHooks(cbtype, content, length)) {
1310 /* above function returns nonzero if it handled the part */
1315 if (strncasecmp(cbtype, "multipart/", 10)) {
1316 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1317 partnum, filename, cbtype, (long)length);
1324 * The client is elegant and sophisticated and wants to be choosy about
1325 * MIME content types, so figure out which multipart/alternative part
1326 * we're going to send.
1328 * We use a system of weights. When we find a part that matches one of the
1329 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1330 * and then set ma->chosen_pref to that MIME type's position in our preference
1331 * list. If we then hit another match, we only replace the first match if
1332 * the preference value is lower.
1334 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1335 void *content, char *cbtype, char *cbcharset, size_t length,
1336 char *encoding, char *cbid, void *cbuserdata)
1342 ma = (struct ma_info *)cbuserdata;
1344 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1345 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1346 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1347 if (i < ma->chosen_pref) {
1348 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1349 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1350 ma->chosen_pref = i;
1358 * Now that we've chosen our preferred part, output it.
1360 void output_preferred(char *name,
1374 int add_newline = 0;
1377 char *decoded = NULL;
1378 size_t bytes_decoded;
1381 ma = (struct ma_info *)cbuserdata;
1383 /* This is not the MIME part you're looking for... */
1384 if (strcasecmp(partnum, ma->chosen_part)) return;
1386 /* If the content-type of this part is in our preferred formats
1387 * list, we can simply output it verbatim.
1389 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1390 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1391 if (!strcasecmp(buf, cbtype)) {
1392 /* Yeah! Go! W00t!! */
1393 if (ma->dont_decode == 0)
1394 rc = mime_decode_now (content,
1400 break; /* Give us the chance, maybe theres another one. */
1402 if (rc == 0) text_content = (char *)content;
1404 text_content = decoded;
1405 length = bytes_decoded;
1408 if (text_content[length-1] != '\n') {
1411 cprintf("Content-type: %s", cbtype);
1412 if (!IsEmptyStr(cbcharset)) {
1413 cprintf("; charset=%s", cbcharset);
1415 cprintf("\nContent-length: %d\n",
1416 (int)(length + add_newline) );
1417 if (!IsEmptyStr(encoding)) {
1418 cprintf("Content-transfer-encoding: %s\n", encoding);
1421 cprintf("Content-transfer-encoding: 7bit\n");
1423 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1425 if (client_write(text_content, length) == -1)
1427 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1430 if (add_newline) cprintf("\n");
1431 if (decoded != NULL) free(decoded);
1436 /* No translations required or possible: output as text/plain */
1437 cprintf("Content-type: text/plain\n\n");
1439 if (ma->dont_decode == 0)
1440 rc = mime_decode_now (content,
1446 return; /* Give us the chance, maybe theres another one. */
1448 if (rc == 0) text_content = (char *)content;
1450 text_content = decoded;
1451 length = bytes_decoded;
1454 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1455 length, encoding, cbid, cbuserdata);
1456 if (decoded != NULL) free(decoded);
1461 char desired_section[64];
1468 * Callback function for
1470 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1471 void *content, char *cbtype, char *cbcharset, size_t length,
1472 char *encoding, char *cbid, void *cbuserdata)
1474 struct encapmsg *encap;
1476 encap = (struct encapmsg *)cbuserdata;
1478 /* Only proceed if this is the desired section... */
1479 if (!strcasecmp(encap->desired_section, partnum)) {
1480 encap->msglen = length;
1481 encap->msg = malloc(length + 2);
1482 memcpy(encap->msg, content, length);
1489 * Determine whether the specified message exists in the cached_msglist
1490 * (This is a security check)
1492 int check_cached_msglist(long msgnum) {
1494 /* cases in which we skip the check */
1495 if (!CC) return om_ok; /* not a session */
1496 if (CC->client_socket <= 0) return om_ok; /* not a client session */
1497 if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1498 if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1501 /* Do a binary search within the cached_msglist for the requested msgnum */
1503 int max = (CC->cached_num_msgs - 1);
1505 while (max >= min) {
1506 int middle = min + (max-min) / 2 ;
1507 if (msgnum == CC->cached_msglist[middle]) {
1510 if (msgnum > CC->cached_msglist[middle]) {
1518 return om_access_denied;
1523 * Get a message off disk. (returns om_* values found in msgbase.h)
1526 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1527 int mode, /* how would you like that message? */
1528 int headers_only, /* eschew the message body? */
1529 int do_proto, /* do Citadel protocol responses? */
1530 int crlf, /* Use CRLF newlines instead of LF? */
1531 char *section, /* NULL or a message/rfc822 section */
1532 int flags, /* various flags; see msgbase.h */
1537 struct CtdlMessage *TheMessage = NULL;
1538 int retcode = CIT_OK;
1539 struct encapmsg encap;
1542 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1544 (section ? section : "<>")
1547 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1550 if (r == om_not_logged_in) {
1551 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1554 cprintf("%d An unknown error has occurred.\n", ERROR);
1561 * Check to make sure the message is actually IN this room
1563 r = check_cached_msglist(msg_num);
1564 if (r == om_access_denied) {
1565 /* Not in the cache? We get ONE shot to check it again. */
1566 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1567 r = check_cached_msglist(msg_num);
1570 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1571 msg_num, CC->room.QRname
1574 if (r == om_access_denied) {
1575 cprintf("%d message %ld was not found in this room\n",
1576 ERROR + HIGHER_ACCESS_REQUIRED,
1585 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1586 * request that we don't even bother loading the body into memory.
1588 if (headers_only == HEADERS_FAST) {
1589 TheMessage = CtdlFetchMessage(msg_num, 0);
1592 TheMessage = CtdlFetchMessage(msg_num, 1);
1595 if (TheMessage == NULL) {
1596 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1597 ERROR + MESSAGE_NOT_FOUND, msg_num);
1598 return(om_no_such_msg);
1601 /* Here is the weird form of this command, to process only an
1602 * encapsulated message/rfc822 section.
1604 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1605 memset(&encap, 0, sizeof encap);
1606 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1607 mime_parser(CM_RANGE(TheMessage, eMesageText),
1608 *extract_encapsulated_message,
1609 NULL, NULL, (void *)&encap, 0
1612 if ((Author != NULL) && (*Author == NULL))
1615 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1617 if ((Address != NULL) && (*Address == NULL))
1620 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1622 if ((MessageID != NULL) && (*MessageID == NULL))
1625 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1627 CM_Free(TheMessage);
1631 encap.msg[encap.msglen] = 0;
1632 TheMessage = convert_internet_message(encap.msg);
1633 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1635 /* Now we let it fall through to the bottom of this
1636 * function, because TheMessage now contains the
1637 * encapsulated message instead of the top-level
1638 * message. Isn't that neat?
1643 cprintf("%d msg %ld has no part %s\n",
1644 ERROR + MESSAGE_NOT_FOUND,
1648 retcode = om_no_such_msg;
1653 /* Ok, output the message now */
1654 if (retcode == CIT_OK)
1655 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1656 if ((Author != NULL) && (*Author == NULL))
1659 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1661 if ((Address != NULL) && (*Address == NULL))
1664 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1666 if ((MessageID != NULL) && (*MessageID == NULL))
1669 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1672 CM_Free(TheMessage);
1678 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1681 char display_name[256];
1683 /* begin header processing loop for Citadel message format */
1684 safestrncpy(display_name, "<unknown>", sizeof display_name);
1685 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1686 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1687 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1688 safestrncpy(display_name, "****", sizeof display_name);
1690 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1691 safestrncpy(display_name, "anonymous", sizeof display_name);
1694 safestrncpy(display_name, buf, sizeof display_name);
1696 if ((is_room_aide())
1697 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1698 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1699 size_t tmp = strlen(display_name);
1700 snprintf(&display_name[tmp],
1701 sizeof display_name - tmp,
1706 /* Now spew the header fields in the order we like them. */
1707 for (i=0; i< NDiskFields; ++i) {
1709 Field = FieldOrder[i];
1710 if (Field != eMesageText) {
1711 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1712 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1713 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1715 if (Field == eAuthor) {
1717 cprintf("%s=%s\n", msgkeys[Field], display_name);
1720 /* Masquerade display name if needed */
1723 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1726 /* Give the client a hint about whether the message originated locally */
1727 if (Field == erFc822Addr) {
1728 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1729 cprintf("locl=yes\n"); // message originated locally.
1741 void OutputRFC822MsgHeaders(
1742 struct CtdlMessage *TheMessage,
1743 int flags, /* should the message be exported clean */
1744 const char *nl, int nlen,
1745 char *mid, long sizeof_mid,
1746 char *suser, long sizeof_suser,
1747 char *luser, long sizeof_luser,
1748 char *fuser, long sizeof_fuser,
1749 char *snode, long sizeof_snode)
1751 char datestamp[100];
1752 int subject_found = 0;
1759 for (i = 0; i < NDiskFields; ++i) {
1760 if (TheMessage->cm_fields[FieldOrder[i]]) {
1761 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1762 switch (FieldOrder[i]) {
1764 safestrncpy(luser, mptr, sizeof_luser);
1765 safestrncpy(suser, mptr, sizeof_suser);
1768 if ((flags & QP_EADDR) != 0) {
1769 mptr = qp_encode_email_addrs(mptr);
1771 sanitize_truncated_recipient(mptr);
1772 cprintf("CC: %s%s", mptr, nl);
1775 cprintf("Return-Path: %s%s", mptr, nl);
1778 cprintf("List-ID: %s%s", mptr, nl);
1781 if ((flags & QP_EADDR) != 0)
1782 mptr = qp_encode_email_addrs(mptr);
1784 while ((*hptr != '\0') && isspace(*hptr))
1786 if (!IsEmptyStr(hptr))
1787 cprintf("Envelope-To: %s%s", hptr, nl);
1790 cprintf("Subject: %s%s", mptr, nl);
1794 safestrncpy(mid, mptr, sizeof_mid);
1797 safestrncpy(fuser, mptr, sizeof_fuser);
1800 if (haschar(mptr, '@') == 0) {
1801 sanitize_truncated_recipient(mptr);
1802 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1806 if ((flags & QP_EADDR) != 0) {
1807 mptr = qp_encode_email_addrs(mptr);
1809 sanitize_truncated_recipient(mptr);
1810 cprintf("To: %s", mptr);
1815 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1816 cprintf("Date: %s%s", datestamp, nl);
1819 cprintf("References: ");
1820 k = num_tokens(mptr, '|');
1821 for (j=0; j<k; ++j) {
1822 extract_token(buf, mptr, j, '|', sizeof buf);
1823 cprintf("<%s>", buf);
1834 while ((*hptr != '\0') && isspace(*hptr))
1836 if (!IsEmptyStr(hptr))
1837 cprintf("Reply-To: %s%s", mptr, nl);
1849 /* these don't map to mime message headers. */
1852 if (mptr != mpptr) {
1857 if (subject_found == 0) {
1858 cprintf("Subject: (no subject)%s", nl);
1863 void Dump_RFC822HeadersBody(
1864 struct CtdlMessage *TheMessage,
1865 int headers_only, /* eschew the message body? */
1866 int flags, /* should the bessage be exported clean? */
1867 const char *nl, int nlen)
1869 cit_uint8_t prev_ch;
1871 const char *StartOfText = StrBufNOTNULL;
1874 int nllen = strlen(nl);
1878 mptr = TheMessage->cm_fields[eMesageText];
1881 while (*mptr != '\0') {
1882 if (*mptr == '\r') {
1889 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1891 eoh = *(mptr+1) == '\n';
1895 StartOfText = strchr(StartOfText, '\n');
1896 StartOfText = strchr(StartOfText, '\n');
1899 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1900 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1901 ((headers_only != HEADERS_NONE) &&
1902 (headers_only != HEADERS_ONLY))
1904 if (*mptr == '\n') {
1905 memcpy(&outbuf[outlen], nl, nllen);
1907 outbuf[outlen] = '\0';
1910 outbuf[outlen++] = *mptr;
1914 if (flags & ESC_DOT) {
1915 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1916 outbuf[outlen++] = '.';
1921 if (outlen > 1000) {
1922 if (client_write(outbuf, outlen) == -1) {
1923 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1926 lfSent = (outbuf[outlen - 1] == '\n');
1931 client_write(outbuf, outlen);
1932 lfSent = (outbuf[outlen - 1] == '\n');
1935 client_write(nl, nlen);
1939 /* If the format type on disk is 1 (fixed-format), then we want
1940 * everything to be output completely literally ... regardless of
1941 * what message transfer format is in use.
1943 void DumpFormatFixed(
1944 struct CtdlMessage *TheMessage,
1945 int mode, /* how would you like that message? */
1946 const char *nl, int nllen)
1954 mptr = TheMessage->cm_fields[eMesageText];
1956 if (mode == MT_MIME) {
1957 cprintf("Content-type: text/plain\n\n");
1961 while (ch = *mptr++, ch > 0) {
1965 if ((buflen > 250) && (!xlline)){
1969 while ((buflen > 0) &&
1970 (!isspace(buf[buflen])))
1976 mptr -= tbuflen - buflen;
1982 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1983 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1988 memcpy (&buf[buflen], nl, nllen);
1992 if (client_write(buf, buflen) == -1) {
1993 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
2005 if (!IsEmptyStr(buf)) {
2006 cprintf("%s%s", buf, nl);
2012 * Get a message off disk. (returns om_* values found in msgbase.h)
2014 int CtdlOutputPreLoadedMsg(
2015 struct CtdlMessage *TheMessage,
2016 int mode, /* how would you like that message? */
2017 int headers_only, /* eschew the message body? */
2018 int do_proto, /* do Citadel protocol responses? */
2019 int crlf, /* Use CRLF newlines instead of LF? */
2020 int flags /* should the bessage be exported clean? */
2023 const char *nl; /* newline string */
2027 /* Buffers needed for RFC822 translation. These are all filled
2028 * using functions that are bounds-checked, and therefore we can
2029 * make them substantially smaller than SIZ.
2037 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
2038 ((TheMessage == NULL) ? "NULL" : "not null"),
2039 mode, headers_only, do_proto, crlf
2042 strcpy(mid, "unknown");
2043 nl = (crlf ? "\r\n" : "\n");
2044 nlen = crlf ? 2 : 1;
2046 if (!CM_IsValidMsg(TheMessage)) {
2047 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2048 return(om_no_such_msg);
2051 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2052 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2054 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2055 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2058 /* Are we downloading a MIME component? */
2059 if (mode == MT_DOWNLOAD) {
2060 if (TheMessage->cm_format_type != FMT_RFC822) {
2062 cprintf("%d This is not a MIME message.\n",
2063 ERROR + ILLEGAL_VALUE);
2064 } else if (CC->download_fp != NULL) {
2065 if (do_proto) cprintf(
2066 "%d You already have a download open.\n",
2067 ERROR + RESOURCE_BUSY);
2069 /* Parse the message text component */
2070 mime_parser(CM_RANGE(TheMessage, eMesageText),
2071 *mime_download, NULL, NULL, NULL, 0);
2072 /* If there's no file open by this time, the requested
2073 * section wasn't found, so print an error
2075 if (CC->download_fp == NULL) {
2076 if (do_proto) cprintf(
2077 "%d Section %s not found.\n",
2078 ERROR + FILE_NOT_FOUND,
2079 CC->download_desired_section);
2082 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2085 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2086 * in a single server operation instead of opening a download file.
2088 if (mode == MT_SPEW_SECTION) {
2089 if (TheMessage->cm_format_type != FMT_RFC822) {
2091 cprintf("%d This is not a MIME message.\n",
2092 ERROR + ILLEGAL_VALUE);
2094 /* Parse the message text component */
2097 mime_parser(CM_RANGE(TheMessage, eMesageText),
2098 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2099 /* If section wasn't found, print an error
2102 if (do_proto) cprintf(
2103 "%d Section %s not found.\n",
2104 ERROR + FILE_NOT_FOUND,
2105 CC->download_desired_section);
2108 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2111 /* now for the user-mode message reading loops */
2112 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2114 /* Does the caller want to skip the headers? */
2115 if (headers_only == HEADERS_NONE) goto START_TEXT;
2117 /* Tell the client which format type we're using. */
2118 if ( (mode == MT_CITADEL) && (do_proto) ) {
2119 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2122 /* nhdr=yes means that we're only displaying headers, no body */
2123 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2124 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2127 cprintf("nhdr=yes\n");
2130 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2131 OutputCtdlMsgHeaders(TheMessage, do_proto);
2134 /* begin header processing loop for RFC822 transfer format */
2139 if (mode == MT_RFC822)
2140 OutputRFC822MsgHeaders(
2145 suser, sizeof(suser),
2146 luser, sizeof(luser),
2147 fuser, sizeof(fuser),
2148 snode, sizeof(snode)
2152 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2153 suser[i] = tolower(suser[i]);
2154 if (!isalnum(suser[i])) suser[i]='_';
2157 if (mode == MT_RFC822) {
2158 /* Construct a fun message id */
2159 cprintf("Message-ID: <%s", mid);
2160 if (strchr(mid, '@')==NULL) {
2161 cprintf("@%s", snode);
2165 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2166 cprintf("From: \"----\" <x@x.org>%s", nl);
2168 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2169 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2171 else if (!IsEmptyStr(fuser)) {
2172 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2175 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2178 /* Blank line signifying RFC822 end-of-headers */
2179 if (TheMessage->cm_format_type != FMT_RFC822) {
2184 /* end header processing loop ... at this point, we're in the text */
2186 if (headers_only == HEADERS_FAST) goto DONE;
2188 /* Tell the client about the MIME parts in this message */
2189 if (TheMessage->cm_format_type == FMT_RFC822) {
2190 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2191 memset(&ma, 0, sizeof(struct ma_info));
2192 mime_parser(CM_RANGE(TheMessage, eMesageText),
2193 (do_proto ? *list_this_part : NULL),
2194 (do_proto ? *list_this_pref : NULL),
2195 (do_proto ? *list_this_suff : NULL),
2198 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2199 Dump_RFC822HeadersBody(
2208 if (headers_only == HEADERS_ONLY) {
2212 /* signify start of msg text */
2213 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2214 if (do_proto) cprintf("text\n");
2217 if (TheMessage->cm_format_type == FMT_FIXED)
2220 mode, /* how would you like that message? */
2223 /* If the message on disk is format 0 (Citadel vari-format), we
2224 * output using the formatter at 80 columns. This is the final output
2225 * form if the transfer format is RFC822, but if the transfer format
2226 * is Citadel proprietary, it'll still work, because the indentation
2227 * for new paragraphs is correct and the client will reformat the
2228 * message to the reader's screen width.
2230 if (TheMessage->cm_format_type == FMT_CITADEL) {
2231 if (mode == MT_MIME) {
2232 cprintf("Content-type: text/x-citadel-variformat\n\n");
2234 memfmout(TheMessage->cm_fields[eMesageText], nl);
2237 /* If the message on disk is format 4 (MIME), we've gotta hand it
2238 * off to the MIME parser. The client has already been told that
2239 * this message is format 1 (fixed format), so the callback function
2240 * we use will display those parts as-is.
2242 if (TheMessage->cm_format_type == FMT_RFC822) {
2243 memset(&ma, 0, sizeof(struct ma_info));
2245 if (mode == MT_MIME) {
2246 ma.use_fo_hooks = 0;
2247 strcpy(ma.chosen_part, "1");
2248 ma.chosen_pref = 9999;
2249 ma.dont_decode = CC->msg4_dont_decode;
2250 mime_parser(CM_RANGE(TheMessage, eMesageText),
2251 *choose_preferred, *fixed_output_pre,
2252 *fixed_output_post, (void *)&ma, 1);
2253 mime_parser(CM_RANGE(TheMessage, eMesageText),
2254 *output_preferred, NULL, NULL, (void *)&ma, 1);
2257 ma.use_fo_hooks = 1;
2258 mime_parser(CM_RANGE(TheMessage, eMesageText),
2259 *fixed_output, *fixed_output_pre,
2260 *fixed_output_post, (void *)&ma, 0);
2265 DONE: /* now we're done */
2266 if (do_proto) cprintf("000\n");
2271 * Save one or more message pointers into a specified room
2272 * (Returns 0 for success, nonzero for failure)
2273 * roomname may be NULL to use the current room
2275 * Note that the 'supplied_msg' field may be set to NULL, in which case
2276 * the message will be fetched from disk, by number, if we need to perform
2277 * replication checks. This adds an additional database read, so if the
2278 * caller already has the message in memory then it should be supplied. (Obviously
2279 * this mode of operation only works if we're saving a single message.)
2281 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2282 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2285 char hold_rm[ROOMNAMELEN];
2286 struct cdbdata *cdbfr;
2289 long highest_msg = 0L;
2292 struct CtdlMessage *msg = NULL;
2294 long *msgs_to_be_merged = NULL;
2295 int num_msgs_to_be_merged = 0;
2298 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2299 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2302 strcpy(hold_rm, CC->room.QRname);
2305 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2306 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2307 if (num_newmsgs > 1) supplied_msg = NULL;
2309 /* Now the regular stuff */
2310 if (CtdlGetRoomLock(&CC->room,
2311 ((roomname != NULL) ? roomname : CC->room.QRname) )
2313 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2314 return(ERROR + ROOM_NOT_FOUND);
2318 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2319 num_msgs_to_be_merged = 0;
2322 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2323 if (cdbfr == NULL) {
2327 msglist = (long *) cdbfr->ptr;
2328 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2329 num_msgs = cdbfr->len / sizeof(long);
2334 /* Create a list of msgid's which were supplied by the caller, but do
2335 * not already exist in the target room. It is absolutely taboo to
2336 * have more than one reference to the same message in a room.
2338 for (i=0; i<num_newmsgs; ++i) {
2340 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2341 if (msglist[j] == newmsgidlist[i]) {
2346 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2350 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2353 * Now merge the new messages
2355 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2356 if (msglist == NULL) {
2357 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2358 free(msgs_to_be_merged);
2359 return (ERROR + INTERNAL_ERROR);
2361 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2362 num_msgs += num_msgs_to_be_merged;
2364 /* Sort the message list, so all the msgid's are in order */
2365 num_msgs = sort_msglist(msglist, num_msgs);
2367 /* Determine the highest message number */
2368 highest_msg = msglist[num_msgs - 1];
2370 /* Write it back to disk. */
2371 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2372 msglist, (int)(num_msgs * sizeof(long)));
2374 /* Free up the memory we used. */
2377 /* Update the highest-message pointer and unlock the room. */
2378 CC->room.QRhighest = highest_msg;
2379 CtdlPutRoomLock(&CC->room);
2381 /* Perform replication checks if necessary */
2382 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2383 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2385 for (i=0; i<num_msgs_to_be_merged; ++i) {
2386 msgid = msgs_to_be_merged[i];
2388 if (supplied_msg != NULL) {
2392 msg = CtdlFetchMessage(msgid, 0);
2396 ReplicationChecks(msg);
2398 /* If the message has an Exclusive ID, index that... */
2399 if (!CM_IsEmpty(msg, eExclusiveID)) {
2400 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2403 /* Free up the memory we may have allocated */
2404 if (msg != supplied_msg) {
2413 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2416 /* Submit this room for processing by hooks */
2417 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2418 if (total_roomhook_errors) {
2419 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2422 /* Go back to the room we were in before we wandered here... */
2423 CtdlGetRoom(&CC->room, hold_rm);
2425 /* Bump the reference count for all messages which were merged */
2426 if (!suppress_refcount_adj) {
2427 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2430 /* Free up memory... */
2431 if (msgs_to_be_merged != NULL) {
2432 free(msgs_to_be_merged);
2435 /* Return success. */
2441 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2444 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2445 int do_repl_check, struct CtdlMessage *supplied_msg)
2447 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2452 * Message base operation to save a new message to the message store
2453 * (returns new message number)
2455 * This is the back end for CtdlSubmitMsg() and should not be directly
2456 * called by server-side modules.
2459 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2467 * If the message is big, set its body aside for storage elsewhere
2468 * and we hide the message body from the serializer
2470 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2472 holdM = msg->cm_fields[eMesageText];
2473 msg->cm_fields[eMesageText] = NULL;
2474 holdMLen = msg->cm_lengths[eMesageText];
2475 msg->cm_lengths[eMesageText] = 0;
2478 /* Serialize our data structure for storage in the database */
2479 CtdlSerializeMessage(&smr, msg);
2482 /* put the message body back into the message */
2483 msg->cm_fields[eMesageText] = holdM;
2484 msg->cm_lengths[eMesageText] = holdMLen;
2489 cprintf("%d Unable to serialize message\n",
2490 ERROR + INTERNAL_ERROR);
2493 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2499 /* Write our little bundle of joy into the message base */
2500 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2502 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2506 retval = cdb_store(CDB_BIGMSGS,
2513 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2518 /* Free the memory we used for the serialized message */
2525 long send_message(struct CtdlMessage *msg) {
2531 /* Get a new message number */
2532 newmsgid = get_new_message_number();
2534 /* Generate an ID if we don't have one already */
2535 if (CM_IsEmpty(msg, emessageId)) {
2536 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2537 (long unsigned int) time(NULL),
2538 (long unsigned int) newmsgid,
2539 CtdlGetConfigStr("c_fqdn")
2542 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2545 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2551 /* Return the *local* message ID to the caller
2552 * (even if we're storing an incoming network message)
2559 * Serialize a struct CtdlMessage into the format used on disk.
2561 * This function loads up a "struct ser_ret" (defined in server.h) which
2562 * contains the length of the serialized message and a pointer to the
2563 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2565 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2566 struct CtdlMessage *msg) /* unserialized msg */
2572 * Check for valid message format
2574 if (CM_IsValidMsg(msg) == 0) {
2575 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2582 for (i=0; i < NDiskFields; ++i)
2583 if (msg->cm_fields[FieldOrder[i]] != NULL)
2584 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2586 ret->ser = malloc(ret->len);
2587 if (ret->ser == NULL) {
2588 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2595 ret->ser[1] = msg->cm_anon_type;
2596 ret->ser[2] = msg->cm_format_type;
2599 for (i=0; i < NDiskFields; ++i) {
2600 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2601 ret->ser[wlen++] = (char)FieldOrder[i];
2603 memcpy(&ret->ser[wlen],
2604 msg->cm_fields[FieldOrder[i]],
2605 msg->cm_lengths[FieldOrder[i]] + 1);
2607 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2611 if (ret->len != wlen) {
2612 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2620 * Check to see if any messages already exist in the current room which
2621 * carry the same Exclusive ID as this one. If any are found, delete them.
2623 void ReplicationChecks(struct CtdlMessage *msg) {
2624 long old_msgnum = (-1L);
2626 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2628 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2630 /* No exclusive id? Don't do anything. */
2631 if (msg == NULL) return;
2632 if (CM_IsEmpty(msg, eExclusiveID)) return;
2634 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2635 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2637 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2638 if (old_msgnum > 0L) {
2639 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2640 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2646 * Save a message to disk and submit it into the delivery system.
2648 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2649 struct recptypes *recps, /* recipients (if mail) */
2650 const char *force /* force a particular room? */
2652 char hold_rm[ROOMNAMELEN];
2653 char actual_rm[ROOMNAMELEN];
2654 char force_room[ROOMNAMELEN];
2655 char content_type[SIZ]; /* We have to learn this */
2656 char recipient[SIZ];
2657 char bounce_to[1024];
2660 const char *mptr = NULL;
2661 struct ctdluser userbuf;
2663 struct MetaData smi;
2664 char *collected_addresses = NULL;
2665 struct addresses_to_be_filed *aptr = NULL;
2666 StrBuf *saved_rfc822_version = NULL;
2667 int qualified_for_journaling = 0;
2669 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2670 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2672 /* If this message has no timestamp, we take the liberty of
2673 * giving it one, right now.
2675 if (CM_IsEmpty(msg, eTimestamp)) {
2676 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2679 /* If this message has no path, we generate one.
2681 if (CM_IsEmpty(msg, eMessagePath)) {
2682 if (!CM_IsEmpty(msg, eAuthor)) {
2683 CM_CopyField(msg, eMessagePath, eAuthor);
2684 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2685 if (isspace(msg->cm_fields[eMessagePath][a])) {
2686 msg->cm_fields[eMessagePath][a] = ' ';
2691 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2695 if (force == NULL) {
2696 force_room[0] = '\0';
2699 strcpy(force_room, force);
2702 /* Learn about what's inside, because it's what's inside that counts */
2703 if (CM_IsEmpty(msg, eMesageText)) {
2704 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2708 switch (msg->cm_format_type) {
2710 strcpy(content_type, "text/x-citadel-variformat");
2713 strcpy(content_type, "text/plain");
2716 strcpy(content_type, "text/plain");
2717 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2720 safestrncpy(content_type, &mptr[13], sizeof content_type);
2721 striplt(content_type);
2722 aptr = content_type;
2723 while (!IsEmptyStr(aptr)) {
2735 /* Goto the correct room */
2736 room = (recps) ? CC->room.QRname : SENTITEMS;
2737 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2738 strcpy(hold_rm, CC->room.QRname);
2739 strcpy(actual_rm, CC->room.QRname);
2740 if (recps != NULL) {
2741 strcpy(actual_rm, SENTITEMS);
2744 /* If the user is a twit, move to the twit room for posting */
2746 if (CC->user.axlevel == AxProbU) {
2747 strcpy(hold_rm, actual_rm);
2748 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2749 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2753 /* ...or if this message is destined for Aide> then go there. */
2754 if (!IsEmptyStr(force_room)) {
2755 strcpy(actual_rm, force_room);
2758 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2759 if (strcasecmp(actual_rm, CC->room.QRname)) {
2760 /* CtdlGetRoom(&CC->room, actual_rm); */
2761 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2765 * If this message has no O (room) field, generate one.
2767 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2768 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2771 /* Perform "before save" hooks (aborting if any return nonzero) */
2772 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2773 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2776 * If this message has an Exclusive ID, and the room is replication
2777 * checking enabled, then do replication checks.
2779 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2780 ReplicationChecks(msg);
2783 /* Save it to disk */
2784 syslog(LOG_DEBUG, "msgbase: saving to disk");
2785 newmsgid = send_message(msg);
2786 if (newmsgid <= 0L) return(-5);
2788 /* Write a supplemental message info record. This doesn't have to
2789 * be a critical section because nobody else knows about this message
2792 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2793 memset(&smi, 0, sizeof(struct MetaData));
2794 smi.meta_msgnum = newmsgid;
2795 smi.meta_refcount = 0;
2796 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2799 * Measure how big this message will be when rendered as RFC822.
2800 * We do this for two reasons:
2801 * 1. We need the RFC822 length for the new metadata record, so the
2802 * POP and IMAP services don't have to calculate message lengths
2803 * while the user is waiting (multiplied by potentially hundreds
2804 * or thousands of messages).
2805 * 2. If journaling is enabled, we will need an RFC822 version of the
2806 * message to attach to the journalized copy.
2808 if (CC->redirect_buffer != NULL) {
2809 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2812 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2813 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2814 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2815 saved_rfc822_version = CC->redirect_buffer;
2816 CC->redirect_buffer = NULL;
2820 /* Now figure out where to store the pointers */
2821 syslog(LOG_DEBUG, "msgbase: storing pointers");
2823 /* If this is being done by the networker delivering a private
2824 * message, we want to BYPASS saving the sender's copy (because there
2825 * is no local sender; it would otherwise go to the Trashcan).
2827 if ((!CC->internal_pgm) || (recps == NULL)) {
2828 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2829 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2830 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2834 /* For internet mail, drop a copy in the outbound queue room */
2835 if ((recps != NULL) && (recps->num_internet > 0)) {
2836 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2839 /* If other rooms are specified, drop them there too. */
2840 if ((recps != NULL) && (recps->num_room > 0)) {
2841 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2842 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2843 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2844 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2848 /* Bump this user's messages posted counter. */
2849 syslog(LOG_DEBUG, "msgbase: updating user");
2850 CtdlLockGetCurrentUser();
2851 CC->user.posted = CC->user.posted + 1;
2852 CtdlPutCurrentUserLock();
2854 /* Decide where bounces need to be delivered */
2855 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2856 if (CC->logged_in) {
2857 strcpy(bounce_to, CC->user.fullname);
2859 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2860 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2862 recps->bounce_to = bounce_to;
2865 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2867 /* If this is private, local mail, make a copy in the
2868 * recipient's mailbox and bump the reference count.
2870 if ((recps != NULL) && (recps->num_local > 0)) {
2874 pch = recps->recp_local;
2875 recps->recp_local = recipient;
2876 ntokens = num_tokens(pch, '|');
2877 for (i=0; i<ntokens; ++i) {
2878 extract_token(recipient, pch, i, '|', sizeof recipient);
2879 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2880 if (CtdlGetUser(&userbuf, recipient) == 0) {
2881 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2882 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2883 CtdlBumpNewMailCounter(userbuf.usernum);
2884 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2887 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2888 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2891 recps->recp_local = pch;
2894 /* Perform "after save" hooks */
2895 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2897 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2898 CM_FlushField(msg, eVltMsgNum);
2900 /* Go back to the room we started from */
2901 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2902 if (strcasecmp(hold_rm, CC->room.QRname)) {
2903 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2907 * Any addresses to harvest for someone's address book?
2909 if ( (CC->logged_in) && (recps != NULL) ) {
2910 collected_addresses = harvest_collected_addresses(msg);
2913 if (collected_addresses != NULL) {
2914 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2915 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2916 aptr->roomname = strdup(actual_rm);
2917 aptr->collected_addresses = collected_addresses;
2918 begin_critical_section(S_ATBF);
2921 end_critical_section(S_ATBF);
2925 * Determine whether this message qualifies for journaling.
2927 if (!CM_IsEmpty(msg, eJournal)) {
2928 qualified_for_journaling = 0;
2931 if (recps == NULL) {
2932 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2934 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2935 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2938 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2943 * Do we have to perform journaling? If so, hand off the saved
2944 * RFC822 version will be handed off to the journaler for background
2945 * submit. Otherwise, we have to free the memory ourselves.
2947 if (saved_rfc822_version != NULL) {
2948 if (qualified_for_journaling) {
2949 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2952 FreeStrBuf(&saved_rfc822_version);
2956 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2957 recps->bounce_to = NULL;
2965 * Convenience function for generating small administrative messages.
2967 long quickie_message(const char *from,
2968 const char *fromaddr,
2973 const char *subject)
2975 struct CtdlMessage *msg;
2976 struct recptypes *recp = NULL;
2978 msg = malloc(sizeof(struct CtdlMessage));
2979 memset(msg, 0, sizeof(struct CtdlMessage));
2980 msg->cm_magic = CTDLMESSAGE_MAGIC;
2981 msg->cm_anon_type = MES_NORMAL;
2982 msg->cm_format_type = format_type;
2984 if (!IsEmptyStr(from)) {
2985 CM_SetField(msg, eAuthor, from, -1);
2987 else if (!IsEmptyStr(fromaddr)) {
2989 CM_SetField(msg, eAuthor, fromaddr, -1);
2990 pAt = strchr(msg->cm_fields[eAuthor], '@');
2992 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2996 msg->cm_fields[eAuthor] = strdup("Citadel");
2999 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
3000 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
3001 if (!IsEmptyStr(to)) {
3002 CM_SetField(msg, eRecipient, to, -1);
3003 recp = validate_recipients(to, NULL, 0);
3005 if (!IsEmptyStr(subject)) {
3006 CM_SetField(msg, eMsgSubject, subject, -1);
3008 if (!IsEmptyStr(text)) {
3009 CM_SetField(msg, eMesageText, text, -1);
3012 long msgnum = CtdlSubmitMsg(msg, recp, room);
3014 if (recp != NULL) free_recipients(recp);
3020 * Back end function used by CtdlMakeMessage() and similar functions
3022 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3024 size_t maxlen, /* maximum message length */
3025 StrBuf *exist, /* if non-null, append to it;
3026 exist is ALWAYS freed */
3027 int crlf /* CRLF newlines instead of LF */
3035 LineBuf = NewStrBufPlain(NULL, SIZ);
3036 if (exist == NULL) {
3037 Message = NewStrBufPlain(NULL, 4 * SIZ);
3040 Message = NewStrBufDup(exist);
3043 /* Do we need to change leading ".." to "." for SMTP escaping? */
3044 if ((tlen == 1) && (*terminator == '.')) {
3048 /* read in the lines of message text one by one */
3050 if (CtdlClientGetLine(LineBuf) < 0) {
3053 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3056 if ( (!flushing) && (!finished) ) {
3058 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3061 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3064 /* Unescape SMTP-style input of two dots at the beginning of the line */
3065 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3066 StrBufCutLeft(LineBuf, 1);
3068 StrBufAppendBuf(Message, LineBuf, 0);
3071 /* if we've hit the max msg length, flush the rest */
3072 if (StrLength(Message) >= maxlen) flushing = 1;
3074 } while (!finished);
3075 FreeStrBuf(&LineBuf);
3081 * Back end function used by CtdlMakeMessage() and similar functions
3083 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3085 size_t maxlen, /* maximum message length */
3086 StrBuf *exist, /* if non-null, append to it;
3087 exist is ALWAYS freed */
3088 int crlf /* CRLF newlines instead of LF */
3093 Message = CtdlReadMessageBodyBuf(terminator,
3099 if (Message == NULL)
3102 return SmashStrBuf(&Message);
3106 struct CtdlMessage *CtdlMakeMessage(
3107 struct ctdluser *author, /* author's user structure */
3108 char *recipient, /* NULL if it's not mail */
3109 char *recp_cc, /* NULL if it's not mail */
3110 char *room, /* room where it's going */
3111 int type, /* see MES_ types in header file */
3112 int format_type, /* variformat, plain text, MIME... */
3113 char *fake_name, /* who we're masquerading as */
3114 char *my_email, /* which of my email addresses to use (empty is ok) */
3115 char *subject, /* Subject (optional) */
3116 char *supplied_euid, /* ...or NULL if this is irrelevant */
3117 char *preformatted_text, /* ...or NULL to read text from client */
3118 char *references /* Thread references */
3120 return CtdlMakeMessageLen(
3121 author, /* author's user structure */
3122 recipient, /* NULL if it's not mail */
3123 (recipient)?strlen(recipient) : 0,
3124 recp_cc, /* NULL if it's not mail */
3125 (recp_cc)?strlen(recp_cc): 0,
3126 room, /* room where it's going */
3127 (room)?strlen(room): 0,
3128 type, /* see MES_ types in header file */
3129 format_type, /* variformat, plain text, MIME... */
3130 fake_name, /* who we're masquerading as */
3131 (fake_name)?strlen(fake_name): 0,
3132 my_email, /* which of my email addresses to use (empty is ok) */
3133 (my_email)?strlen(my_email): 0,
3134 subject, /* Subject (optional) */
3135 (subject)?strlen(subject): 0,
3136 supplied_euid, /* ...or NULL if this is irrelevant */
3137 (supplied_euid)?strlen(supplied_euid):0,
3138 preformatted_text, /* ...or NULL to read text from client */
3139 (preformatted_text)?strlen(preformatted_text) : 0,
3140 references, /* Thread references */
3141 (references)?strlen(references):0);
3147 * Build a binary message to be saved on disk.
3148 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3149 * will become part of the message. This means you are no longer
3150 * responsible for managing that memory -- it will be freed along with
3151 * the rest of the fields when CM_Free() is called.)
3153 struct CtdlMessage *CtdlMakeMessageLen(
3154 struct ctdluser *author, /* author's user structure */
3155 char *recipient, /* NULL if it's not mail */
3157 char *recp_cc, /* NULL if it's not mail */
3159 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 */
3165 char *my_email, /* which of my email addresses to use (empty is ok) */
3167 char *subject, /* Subject (optional) */
3169 char *supplied_euid, /* ...or NULL if this is irrelevant */
3171 char *preformatted_text, /* ...or NULL to read text from client */
3173 char *references, /* Thread references */
3178 struct CtdlMessage *msg;
3180 StrBuf *FakeEncAuthor = NULL;
3182 msg = malloc(sizeof(struct CtdlMessage));
3183 memset(msg, 0, sizeof(struct CtdlMessage));
3184 msg->cm_magic = CTDLMESSAGE_MAGIC;
3185 msg->cm_anon_type = type;
3186 msg->cm_format_type = format_type;
3188 if (recipient != NULL) rcplen = striplt(recipient);
3189 if (recp_cc != NULL) cclen = striplt(recp_cc);
3191 /* Path or Return-Path */
3193 CM_SetField(msg, eMessagePath, my_email, myelen);
3195 else if (!IsEmptyStr(author->fullname)) {
3196 CM_SetField(msg, eMessagePath, author->fullname, -1);
3198 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3200 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3201 CM_SetField(msg, eTimestamp, buf, blen);
3204 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3207 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3209 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3210 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3211 FreeStrBuf(&FakeAuthor);
3213 if (!!IsEmptyStr(CC->room.QRname)) {
3214 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3215 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3218 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3223 CM_SetField(msg, eRecipient, recipient, rcplen);
3226 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3230 CM_SetField(msg, erFc822Addr, my_email, myelen);
3232 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3233 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3236 if (subject != NULL) {
3238 length = striplt(subject);
3244 while ((subject[i] != '\0') &&
3245 (IsAscii = isascii(subject[i]) != 0 ))
3248 CM_SetField(msg, eMsgSubject, subject, subjlen);
3249 else /* ok, we've got utf8 in the string. */
3252 rfc2047Subj = rfc2047encode(subject, length);
3253 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3260 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3264 CM_SetField(msg, eWeferences, references, reflen);
3267 if (preformatted_text != NULL) {
3268 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3272 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3273 if (MsgBody != NULL) {
3274 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3283 * API function to delete messages which match a set of criteria
3284 * (returns the actual number of messages deleted)
3286 int CtdlDeleteMessages(const char *room_name, // which room
3287 long *dmsgnums, // array of msg numbers to be deleted
3288 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3289 char *content_type // or "" for any. regular expressions expected.
3291 struct ctdlroom qrbuf;
3292 struct cdbdata *cdbfr;
3293 long *msglist = NULL;
3294 long *dellist = NULL;
3297 int num_deleted = 0;
3299 struct MetaData smi;
3302 int need_to_free_re = 0;
3304 if (content_type) if (!IsEmptyStr(content_type)) {
3305 regcomp(&re, content_type, 0);
3306 need_to_free_re = 1;
3308 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3310 /* get room record, obtaining a lock... */
3311 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3312 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3313 if (need_to_free_re) regfree(&re);
3314 return(0); /* room not found */
3316 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3318 if (cdbfr != NULL) {
3319 dellist = malloc(cdbfr->len);
3320 msglist = (long *) cdbfr->ptr;
3321 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3322 num_msgs = cdbfr->len / sizeof(long);
3326 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3327 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3328 int have_more_del = 1;
3330 num_msgs = sort_msglist(msglist, num_msgs);
3331 if (num_dmsgnums > 1)
3332 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3335 StrBuf *dbg = NewStrBuf();
3336 for (i = 0; i < num_dmsgnums; i++)
3337 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3338 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3343 while ((i < num_msgs) && (have_more_del)) {
3346 /* Set/clear a bit for each criterion */
3348 /* 0 messages in the list or a null list means that we are
3349 * interested in deleting any messages which meet the other criteria.
3352 delete_this |= 0x01;
3355 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3360 if (msglist[i] == dmsgnums[j]) {
3361 delete_this |= 0x01;
3364 have_more_del = (j < num_dmsgnums);
3367 if (have_contenttype) {
3368 GetMetaData(&smi, msglist[i]);
3369 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3370 delete_this |= 0x02;
3373 delete_this |= 0x02;
3376 /* Delete message only if all bits are set */
3377 if (delete_this == 0x03) {
3378 dellist[num_deleted++] = msglist[i];
3385 StrBuf *dbg = NewStrBuf();
3386 for (i = 0; i < num_deleted; i++)
3387 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3388 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3392 num_msgs = sort_msglist(msglist, num_msgs);
3393 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3394 msglist, (int)(num_msgs * sizeof(long)));
3397 qrbuf.QRhighest = msglist[num_msgs - 1];
3399 qrbuf.QRhighest = 0;
3401 CtdlPutRoomLock(&qrbuf);
3403 /* Go through the messages we pulled out of the index, and decrement
3404 * their reference counts by 1. If this is the only room the message
3405 * was in, the reference count will reach zero and the message will
3406 * automatically be deleted from the database. We do this in a
3407 * separate pass because there might be plug-in hooks getting called,
3408 * and we don't want that happening during an S_ROOMS critical
3412 for (i=0; i<num_deleted; ++i) {
3413 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3415 AdjRefCountList(dellist, num_deleted, -1);
3417 /* Now free the memory we used, and go away. */
3418 if (msglist != NULL) free(msglist);
3419 if (dellist != NULL) free(dellist);
3420 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3421 if (need_to_free_re) regfree(&re);
3422 return (num_deleted);
3427 * GetMetaData() - Get the supplementary record for a message
3429 void GetMetaData(struct MetaData *smibuf, long msgnum)
3431 struct cdbdata *cdbsmi;
3434 memset(smibuf, 0, sizeof(struct MetaData));
3435 smibuf->meta_msgnum = msgnum;
3436 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3438 /* Use the negative of the message number for its supp record index */
3439 TheIndex = (0L - msgnum);
3441 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3442 if (cdbsmi == NULL) {
3443 return; /* record not found; leave it alone */
3445 memcpy(smibuf, cdbsmi->ptr,
3446 ((cdbsmi->len > sizeof(struct MetaData)) ?
3447 sizeof(struct MetaData) : cdbsmi->len)
3455 * PutMetaData() - (re)write supplementary record for a message
3457 void PutMetaData(struct MetaData *smibuf)
3461 /* Use the negative of the message number for the metadata db index */
3462 TheIndex = (0L - smibuf->meta_msgnum);
3464 cdb_store(CDB_MSGMAIN,
3465 &TheIndex, (int)sizeof(long),
3466 smibuf, (int)sizeof(struct MetaData)
3472 * Convenience function to process a big block of AdjRefCount() operations
3474 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3478 for (i = 0; i < nmsg; i++) {
3479 AdjRefCount(msgnum[i], incr);
3485 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3487 void AdjRefCount(long msgnum, int incr)
3489 struct MetaData smi;
3492 /* This is a *tight* critical section; please keep it that way, as
3493 * it may get called while nested in other critical sections.
3494 * Complicating this any further will surely cause deadlock!
3496 begin_critical_section(S_SUPPMSGMAIN);
3497 GetMetaData(&smi, msgnum);
3498 smi.meta_refcount += incr;
3500 end_critical_section(S_SUPPMSGMAIN);
3501 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3503 /* If the reference count is now zero, delete both the message and its metadata record.
3505 if (smi.meta_refcount == 0) {
3506 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3508 /* Call delete hooks with NULL room to show it has gone altogether */
3509 PerformDeleteHooks(NULL, msgnum);
3511 /* Remove from message base */
3513 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3514 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3516 /* Remove metadata record */
3517 delnum = (0L - msgnum);
3518 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3524 * Write a generic object to this room
3526 * Note: this could be much more efficient. Right now we use two temporary
3527 * files, and still pull the message into memory as with all others.
3529 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3530 char *content_type, /* MIME type of this object */
3531 char *raw_message, /* Data to be written */
3532 off_t raw_length, /* Size of raw_message */
3533 struct ctdluser *is_mailbox, /* Mailbox room? */
3534 int is_binary, /* Is encoding necessary? */
3535 int is_unique, /* Del others of this type? */
3536 unsigned int flags /* Internal save flags */
3538 struct ctdlroom qrbuf;
3539 char roomname[ROOMNAMELEN];
3540 struct CtdlMessage *msg;
3541 StrBuf *encoded_message = NULL;
3543 if (is_mailbox != NULL) {
3544 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3547 safestrncpy(roomname, req_room, sizeof(roomname));
3550 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3553 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3556 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3559 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3560 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3561 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3564 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3567 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3571 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3574 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3577 syslog(LOG_DEBUG, "msgbase: allocating");
3578 msg = malloc(sizeof(struct CtdlMessage));
3579 memset(msg, 0, sizeof(struct CtdlMessage));
3580 msg->cm_magic = CTDLMESSAGE_MAGIC;
3581 msg->cm_anon_type = MES_NORMAL;
3582 msg->cm_format_type = 4;
3583 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3584 CM_SetField(msg, eOriginalRoom, req_room, -1);
3585 msg->cm_flags = flags;
3587 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3589 /* Create the requested room if we have to. */
3590 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3591 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3593 /* If the caller specified this object as unique, delete all
3594 * other objects of this type that are currently in the room.
3597 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3598 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3601 /* Now write the data */
3602 CtdlSubmitMsg(msg, NULL, roomname);
3607 /************************************************************************/
3608 /* MODULE INITIALIZATION */
3609 /************************************************************************/
3611 CTDL_MODULE_INIT(msgbase)
3614 FillMsgKeyLookupTable();
3617 /* return our module id for the log */