1 // Implements the message store.
3 // Copyright (c) 1987-2021 by the citadel.org team
5 // This program is open source software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License version 3.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
19 #include <libcitadel.h>
20 #include "ctdl_module.h"
21 #include "citserver.h"
24 #include "clientsocket.h"
28 #include "internet_addressing.h"
29 #include "euidindex.h"
31 #include "journaling.h"
33 struct addresses_to_be_filed *atbf = NULL;
35 // These are the four-character field headers we use when outputting
36 // messages in Citadel format (as opposed to RFC822 format).
38 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
40 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
41 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
47 "from", // A -> eAuthor
48 NULL, // B -> eBig_message
49 NULL, // C (formerly used as eRemoteRoom)
50 NULL, // D (formerly used as eDestination)
51 "exti", // E -> eXclusivID
52 "rfca", // F -> erFc822Addr
54 "hnod", // H (formerly used as eHumanNode)
55 "msgn", // I -> emessageId
56 "jrnl", // J -> eJournal
57 "rep2", // K -> eReplyTo
58 "list", // L -> eListID
59 "text", // M -> eMesageText
60 NULL, // N (formerly used as eNodename)
61 "room", // O -> eOriginalRoom
62 "path", // P -> eMessagePath
64 "rcpt", // R -> eRecipient
65 NULL, // S (formerly used as eSpecialField)
66 "time", // T -> eTimestamp
67 "subj", // U -> eMsgSubject
68 "nvto", // V -> eenVelopeTo
69 "wefw", // W -> eWeferences
71 "cccc", // Y -> eCarbonCopY
76 HashList *msgKeyLookup = NULL;
78 int GetFieldFromMnemonic(eMsgField *f, const char* c) {
80 if (GetHash(msgKeyLookup, c, 4, &v)) {
87 void FillMsgKeyLookupTable(void) {
90 msgKeyLookup = NewHash (1, FourHash);
92 for (i=0; i < 91; i++) {
93 if (msgkeys[i] != NULL) {
94 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
100 eMsgField FieldOrder[] = {
101 /* Important fields */
109 /* Semi-important fields */
114 /* G is not used yet */
117 /* Q is not used yet */
119 /* X is not used yet */
120 /* Z is not used yet */
127 /* Message text (MUST be last) */
129 /* Not saved to disk:
134 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
137 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
138 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
142 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
143 if (Msg->cm_fields[which] != NULL) {
144 free (Msg->cm_fields[which]);
146 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
147 length = strlen(buf);
149 Msg->cm_fields[which] = malloc(length + 1);
150 memcpy(Msg->cm_fields[which], buf, length);
151 Msg->cm_fields[which][length] = '\0';
152 Msg->cm_lengths[which] = length;
156 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
159 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
160 CM_SetField(Msg, which, buf, len);
164 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
165 if (Msg->cm_fields[WhichToCut] == NULL)
168 if (Msg->cm_lengths[WhichToCut] > maxlen)
170 Msg->cm_fields[WhichToCut][maxlen] = '\0';
171 Msg->cm_lengths[WhichToCut] = maxlen;
176 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
177 if (Msg->cm_fields[which] != NULL)
178 free (Msg->cm_fields[which]);
179 Msg->cm_fields[which] = NULL;
180 Msg->cm_lengths[which] = 0;
184 void CM_Flush(struct CtdlMessage *Msg) {
187 if (CM_IsValidMsg(Msg) == 0) {
191 for (i = 0; i < 256; ++i) {
192 CM_FlushField(Msg, i);
197 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
199 if (Msg->cm_fields[WhichToPutTo] != NULL) {
200 free (Msg->cm_fields[WhichToPutTo]);
203 if (Msg->cm_fields[WhichtToCopy] != NULL) {
204 len = Msg->cm_lengths[WhichtToCopy];
205 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
206 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
207 Msg->cm_fields[WhichToPutTo][len] = '\0';
208 Msg->cm_lengths[WhichToPutTo] = len;
211 Msg->cm_fields[WhichToPutTo] = NULL;
212 Msg->cm_lengths[WhichToPutTo] = 0;
217 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
218 if (Msg->cm_fields[which] != NULL) {
223 oldmsgsize = Msg->cm_lengths[which] + 1;
224 newmsgsize = length + oldmsgsize;
226 new = malloc(newmsgsize);
227 memcpy(new, buf, length);
228 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
229 free(Msg->cm_fields[which]);
230 Msg->cm_fields[which] = new;
231 Msg->cm_lengths[which] = newmsgsize - 1;
234 Msg->cm_fields[which] = malloc(length + 1);
235 memcpy(Msg->cm_fields[which], buf, length);
236 Msg->cm_fields[which][length] = '\0';
237 Msg->cm_lengths[which] = length;
242 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
243 if (Msg->cm_fields[which] != NULL) {
244 free (Msg->cm_fields[which]);
247 Msg->cm_fields[which] = *buf;
249 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
250 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
253 Msg->cm_lengths[which] = length;
258 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
259 if (Msg->cm_fields[which] != NULL) {
260 free (Msg->cm_fields[which]);
263 Msg->cm_lengths[which] = StrLength(*buf);
264 Msg->cm_fields[which] = SmashStrBuf(buf);
268 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
269 if (Msg->cm_fields[which] != NULL) {
270 *retlen = Msg->cm_lengths[which];
271 *ret = Msg->cm_fields[which];
272 Msg->cm_fields[which] = NULL;
273 Msg->cm_lengths[which] = 0;
282 // Returns 1 if the supplied pointer points to a valid Citadel message.
283 // If the pointer is NULL or the magic number check fails, returns 0.
284 int CM_IsValidMsg(struct CtdlMessage *msg) {
288 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
289 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
296 void CM_FreeContents(struct CtdlMessage *msg) {
299 for (i = 0; i < 256; ++i)
300 if (msg->cm_fields[i] != NULL) {
301 free(msg->cm_fields[i]);
302 msg->cm_lengths[i] = 0;
305 msg->cm_magic = 0; // just in case
309 // 'Destructor' for struct CtdlMessage
310 void CM_Free(struct CtdlMessage *msg) {
311 if (CM_IsValidMsg(msg) == 0) {
312 if (msg != NULL) free (msg);
315 CM_FreeContents(msg);
320 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
322 len = OrgMsg->cm_lengths[i];
323 NewMsg->cm_fields[i] = malloc(len + 1);
324 if (NewMsg->cm_fields[i] == NULL) {
327 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
328 NewMsg->cm_fields[i][len] = '\0';
329 NewMsg->cm_lengths[i] = len;
334 struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
336 struct CtdlMessage *NewMsg;
338 if (CM_IsValidMsg(OrgMsg) == 0) {
341 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
342 if (NewMsg == NULL) {
346 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
348 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
350 for (i = 0; i < 256; ++i) {
351 if (OrgMsg->cm_fields[i] != NULL) {
352 if (!CM_DupField(i, OrgMsg, NewMsg)) {
363 // Determine if a given message matches the fields in a message template.
364 // Return 0 for a successful match.
365 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
368 // If there aren't any fields in the template, all messages will match.
369 if (template == NULL) return(0);
371 // Null messages are bogus.
372 if (msg == NULL) return(1);
374 for (i='A'; i<='Z'; ++i) {
375 if (template->cm_fields[i] != NULL) {
376 if (msg->cm_fields[i] == NULL) {
377 // Considered equal if temmplate is empty string
378 if (IsEmptyStr(template->cm_fields[i])) continue;
381 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
382 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
387 /* All compares succeeded: we have a match! */
392 // Retrieve the "seen" message list for the current room.
393 void CtdlGetSeen(char *buf, int which_set) {
396 // Learn about the user and room in question
397 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
399 if (which_set == ctdlsetseen_seen) {
400 safestrncpy(buf, vbuf.v_seen, SIZ);
402 if (which_set == ctdlsetseen_answered) {
403 safestrncpy(buf, vbuf.v_answered, SIZ);
408 // Manipulate the "seen msgs" string (or other message set strings)
409 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
410 int target_setting, int which_set,
411 struct ctdluser *which_user, struct ctdlroom *which_room) {
412 struct cdbdata *cdbfr;
426 char *is_set; /* actually an array of booleans */
428 /* Don't bother doing *anything* if we were passed a list of zero messages */
429 if (num_target_msgnums < 1) {
433 /* If no room was specified, we go with the current room. */
435 which_room = &CC->room;
438 /* If no user was specified, we go with the current user. */
440 which_user = &CC->user;
443 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
444 num_target_msgnums, target_msgnums[0],
445 (target_setting ? "SET" : "CLEAR"),
449 /* Learn about the user and room in question */
450 CtdlGetRelationship(&vbuf, which_user, which_room);
452 /* Load the message list */
453 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
455 msglist = (long *) cdbfr->ptr;
456 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
457 num_msgs = cdbfr->len / sizeof(long);
461 return; /* No messages at all? No further action. */
464 is_set = malloc(num_msgs * sizeof(char));
465 memset(is_set, 0, (num_msgs * sizeof(char)) );
467 /* Decide which message set we're manipulating */
469 case ctdlsetseen_seen:
470 vset = NewStrBufPlain(vbuf.v_seen, -1);
472 case ctdlsetseen_answered:
473 vset = NewStrBufPlain(vbuf.v_answered, -1);
480 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
481 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
482 for (i=0; i<num_msgs; ++i) {
483 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
485 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
486 for (k=0; k<num_target_msgnums; ++k) {
487 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
491 /* Translate the existing sequence set into an array of booleans */
492 setstr = NewStrBuf();
496 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
498 StrBufExtract_token(lostr, setstr, 0, ':');
499 if (StrBufNum_tokens(setstr, ':') >= 2) {
500 StrBufExtract_token(histr, setstr, 1, ':');
504 StrBufAppendBuf(histr, lostr, 0);
507 if (!strcmp(ChrPtr(histr), "*")) {
514 for (i = 0; i < num_msgs; ++i) {
515 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
524 /* Now translate the array of booleans back into a sequence set */
530 for (i=0; i<num_msgs; ++i) {
534 for (k=0; k<num_target_msgnums; ++k) {
535 if (msglist[i] == target_msgnums[k]) {
536 is_seen = target_setting;
540 if ((was_seen == 0) && (is_seen == 1)) {
543 else if ((was_seen == 1) && (is_seen == 0)) {
546 if (StrLength(vset) > 0) {
547 StrBufAppendBufPlain(vset, HKEY(","), 0);
550 StrBufAppendPrintf(vset, "%ld", hi);
553 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
557 if ((is_seen) && (i == num_msgs - 1)) {
558 if (StrLength(vset) > 0) {
559 StrBufAppendBufPlain(vset, HKEY(","), 0);
561 if ((i==0) || (was_seen == 0)) {
562 StrBufAppendPrintf(vset, "%ld", msglist[i]);
565 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
573 * We will have to stuff this string back into a 4096 byte buffer, so if it's
574 * larger than that now, truncate it by removing tokens from the beginning.
575 * The limit of 100 iterations is there to prevent an infinite loop in case
576 * something unexpected happens.
578 int number_of_truncations = 0;
579 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
580 StrBufRemove_token(vset, 0, ',');
581 ++number_of_truncations;
585 * If we're truncating the sequence set of messages marked with the 'seen' flag,
586 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
587 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
589 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
591 first_tok = NewStrBuf();
592 StrBufExtract_token(first_tok, vset, 0, ',');
593 StrBufRemove_token(vset, 0, ',');
595 if (StrBufNum_tokens(first_tok, ':') > 1) {
596 StrBufRemove_token(first_tok, 0, ':');
600 new_set = NewStrBuf();
601 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
602 StrBufAppendBuf(new_set, first_tok, 0);
603 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
604 StrBufAppendBuf(new_set, vset, 0);
607 FreeStrBuf(&first_tok);
611 /* Decide which message set we're manipulating */
613 case ctdlsetseen_seen:
614 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
616 case ctdlsetseen_answered:
617 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
623 CtdlSetRelationship(&vbuf, which_user, which_room);
629 * API function to perform an operation for each qualifying message in the
630 * current room. (Returns the number of messages processed.)
632 int CtdlForEachMessage(int mode, long ref, char *search_string,
634 struct CtdlMessage *compare,
635 ForEachMsgCallback CallBack,
640 struct cdbdata *cdbfr;
641 long *msglist = NULL;
643 int num_processed = 0;
646 struct CtdlMessage *msg = NULL;
649 int printed_lastold = 0;
650 int num_search_msgs = 0;
651 long *search_msgs = NULL;
653 int need_to_free_re = 0;
656 if ((content_type) && (!IsEmptyStr(content_type))) {
657 regcomp(&re, content_type, 0);
661 /* Learn about the user and room in question */
662 if (server_shutting_down) {
663 if (need_to_free_re) regfree(&re);
666 CtdlGetUser(&CC->user, CC->curr_user);
668 if (server_shutting_down) {
669 if (need_to_free_re) regfree(&re);
672 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
674 if (server_shutting_down) {
675 if (need_to_free_re) regfree(&re);
679 /* Load the message list */
680 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
682 if (need_to_free_re) regfree(&re);
683 return 0; /* No messages at all? No further action. */
686 msglist = (long *) cdbfr->ptr;
687 num_msgs = cdbfr->len / sizeof(long);
689 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
690 cdb_free(cdbfr); /* we own this memory now */
693 * Now begin the traversal.
695 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
697 /* If the caller is looking for a specific MIME type, filter
698 * out all messages which are not of the type requested.
700 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
702 /* This call to GetMetaData() sits inside this loop
703 * so that we only do the extra database read per msg
704 * if we need to. Doing the extra read all the time
705 * really kills the server. If we ever need to use
706 * metadata for another search criterion, we need to
707 * move the read somewhere else -- but still be smart
708 * enough to only do the read if the caller has
709 * specified something that will need it.
711 if (server_shutting_down) {
712 if (need_to_free_re) regfree(&re);
716 GetMetaData(&smi, msglist[a]);
718 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
719 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
725 num_msgs = sort_msglist(msglist, num_msgs);
727 /* If a template was supplied, filter out the messages which
728 * don't match. (This could induce some delays!)
731 if (compare != NULL) {
732 for (a = 0; a < num_msgs; ++a) {
733 if (server_shutting_down) {
734 if (need_to_free_re) regfree(&re);
738 msg = CtdlFetchMessage(msglist[a], 1);
740 if (CtdlMsgCmp(msg, compare)) {
749 /* If a search string was specified, get a message list from
750 * the full text index and remove messages which aren't on both
754 * Since the lists are sorted and strictly ascending, and the
755 * output list is guaranteed to be shorter than or equal to the
756 * input list, we overwrite the bottom of the input list. This
757 * eliminates the need to memmove big chunks of the list over and
760 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
762 /* Call search module via hook mechanism.
763 * NULL means use any search function available.
764 * otherwise replace with a char * to name of search routine
766 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
768 if (num_search_msgs > 0) {
772 orig_num_msgs = num_msgs;
774 for (i=0; i<orig_num_msgs; ++i) {
775 for (j=0; j<num_search_msgs; ++j) {
776 if (msglist[i] == search_msgs[j]) {
777 msglist[num_msgs++] = msglist[i];
783 num_msgs = 0; /* No messages qualify */
785 if (search_msgs != NULL) free(search_msgs);
787 /* Now that we've purged messages which don't contain the search
788 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
795 * Now iterate through the message list, according to the
796 * criteria supplied by the caller.
799 for (a = 0; a < num_msgs; ++a) {
800 if (server_shutting_down) {
801 if (need_to_free_re) regfree(&re);
803 return num_processed;
805 thismsg = msglist[a];
806 if (mode == MSGS_ALL) {
810 is_seen = is_msg_in_sequence_set(
811 vbuf.v_seen, thismsg);
812 if (is_seen) lastold = thismsg;
818 || ((mode == MSGS_OLD) && (is_seen))
819 || ((mode == MSGS_NEW) && (!is_seen))
820 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
821 || ((mode == MSGS_FIRST) && (a < ref))
822 || ((mode == MSGS_GT) && (thismsg > ref))
823 || ((mode == MSGS_LT) && (thismsg < ref))
824 || ((mode == MSGS_EQ) && (thismsg == ref))
827 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
829 CallBack(lastold, userdata);
835 CallBack(thismsg, userdata);
840 if (need_to_free_re) regfree(&re);
843 * We cache the most recent msglist in order to do security checks later
845 if (CC->client_socket > 0) {
846 if (CC->cached_msglist != NULL) {
847 free(CC->cached_msglist);
849 CC->cached_msglist = msglist;
850 CC->cached_num_msgs = num_msgs;
856 return num_processed;
861 * memfmout() - Citadel text formatter and paginator.
862 * Although the original purpose of this routine was to format
863 * text to the reader's screen width, all we're really using it
864 * for here is to format text out to 80 columns before sending it
865 * to the client. The client software may reformat it again.
868 char *mptr, /* where are we going to get our text from? */
869 const char *nl /* string to terminate lines with */
872 unsigned char ch = 0;
879 while (ch=*(mptr++), ch != 0) {
882 if (client_write(outbuf, len) == -1) {
883 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
887 if (client_write(nl, nllen) == -1) {
888 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
893 else if (ch == '\r') {
894 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
896 else if (isspace(ch)) {
897 if (column > 72) { /* Beyond 72 columns, break on the next space */
898 if (client_write(outbuf, len) == -1) {
899 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
903 if (client_write(nl, nllen) == -1) {
904 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
917 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
918 if (client_write(outbuf, len) == -1) {
919 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
923 if (client_write(nl, nllen) == -1) {
924 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
932 if (client_write(outbuf, len) == -1) {
933 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
936 client_write(nl, nllen);
943 * Callback function for mime parser that simply lists the part
945 void list_this_part(char *name, char *filename, char *partnum, char *disp,
946 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
947 char *cbid, void *cbuserdata)
951 ma = (struct ma_info *)cbuserdata;
952 if (ma->is_ma == 0) {
953 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
967 * Callback function for multipart prefix
969 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
970 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
971 char *cbid, void *cbuserdata)
975 ma = (struct ma_info *)cbuserdata;
976 if (!strcasecmp(cbtype, "multipart/alternative")) {
980 if (ma->is_ma == 0) {
981 cprintf("pref=%s|%s\n", partnum, cbtype);
987 * Callback function for multipart sufffix
989 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
990 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
991 char *cbid, void *cbuserdata)
995 ma = (struct ma_info *)cbuserdata;
996 if (ma->is_ma == 0) {
997 cprintf("suff=%s|%s\n", partnum, cbtype);
999 if (!strcasecmp(cbtype, "multipart/alternative")) {
1006 * Callback function for mime parser that opens a section for downloading
1007 * we use serv_files function here:
1009 extern void OpenCmdResult(char *filename, const char *mime_type);
1010 void mime_download(char *name, char *filename, char *partnum, char *disp,
1011 void *content, char *cbtype, char *cbcharset, size_t length,
1012 char *encoding, char *cbid, void *cbuserdata)
1016 /* Silently go away if there's already a download open. */
1017 if (CC->download_fp != NULL)
1021 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1022 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1024 CC->download_fp = tmpfile();
1025 if (CC->download_fp == NULL) {
1026 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
1027 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
1031 rv = fwrite(content, length, 1, CC->download_fp);
1033 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
1034 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
1035 fclose(CC->download_fp);
1036 CC->download_fp = NULL;
1039 fflush(CC->download_fp);
1040 rewind(CC->download_fp);
1042 OpenCmdResult(filename, cbtype);
1048 * Callback function for mime parser that outputs a section all at once.
1049 * We can specify the desired section by part number *or* content-id.
1051 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1052 void *content, char *cbtype, char *cbcharset, size_t length,
1053 char *encoding, char *cbid, void *cbuserdata)
1055 int *found_it = (int *)cbuserdata;
1058 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1059 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1062 cprintf("%d %d|-1|%s|%s|%s\n",
1069 client_write(content, length);
1074 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1076 struct CtdlMessage *ret = NULL;
1078 const char *upper_bound;
1080 cit_uint8_t field_header;
1084 upper_bound = Buffer + Length;
1089 // Parse the three bytes that begin EVERY message on disk.
1090 // The first is always 0xFF, the on-disk magic number.
1091 // The second is the anonymous/public type byte.
1092 // The third is the format type byte (vari, fixed, or MIME).
1096 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1099 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1100 memset(ret, 0, sizeof(struct CtdlMessage));
1102 ret->cm_magic = CTDLMESSAGE_MAGIC;
1103 ret->cm_anon_type = *mptr++; // Anon type byte
1104 ret->cm_format_type = *mptr++; // Format type byte
1106 // The rest is zero or more arbitrary fields. Load them in.
1107 // We're done when we encounter either a zero-length field or
1108 // have just processed the 'M' (message text) field.
1111 field_header = '\0';
1114 while (field_header == '\0') { // work around possibly buggy messages
1115 if (mptr >= upper_bound) {
1118 field_header = *mptr++;
1120 if (mptr >= upper_bound) {
1123 which = field_header;
1126 CM_SetField(ret, which, mptr, len);
1128 mptr += len + 1; // advance to next field
1130 } while ((mptr < upper_bound) && (field_header != 'M'));
1135 // Load a message from disk into memory.
1136 // This is used by CtdlOutputMsg() and other fetch functions.
1138 // NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1139 // using the CM_Free(); function.
1141 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1142 struct cdbdata *dmsgtext;
1143 struct CtdlMessage *ret = NULL;
1145 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1146 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1147 if (dmsgtext == NULL) {
1148 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1152 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
1153 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1154 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1157 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1165 // Always make sure there's something in the msg text field. If
1166 // it's NULL, the message text is most likely stored separately,
1167 // so go ahead and fetch that. Failing that, just set a dummy
1168 // body so other code doesn't barf.
1170 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1171 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1172 if (dmsgtext != NULL) {
1173 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1177 if (CM_IsEmpty(ret, eMesageText)) {
1178 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1185 // Pre callback function for multipart/alternative
1187 // NOTE: this differs from the standard behavior for a reason. Normally when
1188 // displaying multipart/alternative you want to show the _last_ usable
1189 // format in the message. Here we show the _first_ one, because it's
1190 // usually text/plain. Since this set of functions is designed for text
1191 // output to non-MIME-aware clients, this is the desired behavior.
1193 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1194 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1195 char *cbid, void *cbuserdata)
1199 ma = (struct ma_info *)cbuserdata;
1200 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1201 if (!strcasecmp(cbtype, "multipart/alternative")) {
1205 if (!strcasecmp(cbtype, "message/rfc822")) {
1212 // Post callback function for multipart/alternative
1214 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1215 void *content, char *cbtype, char *cbcharset, size_t length,
1216 char *encoding, char *cbid, void *cbuserdata)
1220 ma = (struct ma_info *)cbuserdata;
1221 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1222 if (!strcasecmp(cbtype, "multipart/alternative")) {
1226 if (!strcasecmp(cbtype, "message/rfc822")) {
1232 // Inline callback function for mime parser that wants to display text
1234 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1235 void *content, char *cbtype, char *cbcharset, size_t length,
1236 char *encoding, char *cbid, void *cbuserdata)
1243 ma = (struct ma_info *)cbuserdata;
1246 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1247 partnum, filename, cbtype, (long)length
1250 // If we're in the middle of a multipart/alternative scope and
1251 // we've already printed another section, skip this one.
1253 if ( (ma->is_ma) && (ma->did_print) ) {
1254 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1259 if ( (!strcasecmp(cbtype, "text/plain"))
1260 || (IsEmptyStr(cbtype)) ) {
1263 client_write(wptr, length);
1264 if (wptr[length-1] != '\n') {
1271 if (!strcasecmp(cbtype, "text/html")) {
1272 ptr = html_to_ascii(content, length, 80);
1274 client_write(ptr, wlen);
1275 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1282 if (ma->use_fo_hooks) {
1283 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1288 if (strncasecmp(cbtype, "multipart/", 10)) {
1289 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1290 partnum, filename, cbtype, (long)length);
1296 // The client is elegant and sophisticated and wants to be choosy about
1297 // MIME content types, so figure out which multipart/alternative part
1298 // we're going to send.
1300 // We use a system of weights. When we find a part that matches one of the
1301 // MIME types we've declared as preferential, we can store it in ma->chosen_part
1302 // and then set ma->chosen_pref to that MIME type's position in our preference
1303 // list. If we then hit another match, we only replace the first match if
1304 // the preference value is lower.
1306 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1307 void *content, char *cbtype, char *cbcharset, size_t length,
1308 char *encoding, char *cbid, void *cbuserdata)
1314 ma = (struct ma_info *)cbuserdata;
1316 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1317 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1318 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1319 if (i < ma->chosen_pref) {
1320 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1321 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1322 ma->chosen_pref = i;
1329 // Now that we've chosen our preferred part, output it.
1331 void output_preferred(char *name,
1345 int add_newline = 0;
1348 char *decoded = NULL;
1349 size_t bytes_decoded;
1352 ma = (struct ma_info *)cbuserdata;
1354 // This is not the MIME part you're looking for...
1355 if (strcasecmp(partnum, ma->chosen_part)) return;
1357 // If the content-type of this part is in our preferred formats
1358 // list, we can simply output it verbatim.
1359 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1360 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1361 if (!strcasecmp(buf, cbtype)) {
1362 /* Yeah! Go! W00t!! */
1363 if (ma->dont_decode == 0)
1364 rc = mime_decode_now (content,
1370 break; // Give us the chance, maybe theres another one.
1372 if (rc == 0) text_content = (char *)content;
1374 text_content = decoded;
1375 length = bytes_decoded;
1378 if (text_content[length-1] != '\n') {
1381 cprintf("Content-type: %s", cbtype);
1382 if (!IsEmptyStr(cbcharset)) {
1383 cprintf("; charset=%s", cbcharset);
1385 cprintf("\nContent-length: %d\n",
1386 (int)(length + add_newline) );
1387 if (!IsEmptyStr(encoding)) {
1388 cprintf("Content-transfer-encoding: %s\n", encoding);
1391 cprintf("Content-transfer-encoding: 7bit\n");
1393 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1395 if (client_write(text_content, length) == -1)
1397 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1400 if (add_newline) cprintf("\n");
1401 if (decoded != NULL) free(decoded);
1406 // No translations required or possible: output as text/plain
1407 cprintf("Content-type: text/plain\n\n");
1409 if (ma->dont_decode == 0)
1410 rc = mime_decode_now (content,
1416 return; // 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 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1425 if (decoded != NULL) free(decoded);
1430 char desired_section[64];
1436 // Callback function
1437 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1438 void *content, char *cbtype, char *cbcharset, size_t length,
1439 char *encoding, char *cbid, void *cbuserdata)
1441 struct encapmsg *encap;
1443 encap = (struct encapmsg *)cbuserdata;
1445 // Only proceed if this is the desired section...
1446 if (!strcasecmp(encap->desired_section, partnum)) {
1447 encap->msglen = length;
1448 encap->msg = malloc(length + 2);
1449 memcpy(encap->msg, content, length);
1455 // Determine whether the specified message exists in the cached_msglist
1456 // (This is a security check)
1457 int check_cached_msglist(long msgnum) {
1459 // cases in which we skip the check
1460 if (!CC) return om_ok; // not a session
1461 if (CC->client_socket <= 0) return om_ok; // not a client session
1462 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1463 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1465 // Do a binary search within the cached_msglist for the requested msgnum
1467 int max = (CC->cached_num_msgs - 1);
1469 while (max >= min) {
1470 int middle = min + (max-min) / 2 ;
1471 if (msgnum == CC->cached_msglist[middle]) {
1474 if (msgnum > CC->cached_msglist[middle]) {
1482 return om_access_denied;
1486 // Get a message off disk. (returns om_* values found in msgbase.h)
1487 int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1488 int mode, // how would you like that message?
1489 int headers_only, // eschew the message body?
1490 int do_proto, // do Citadel protocol responses?
1491 int crlf, // Use CRLF newlines instead of LF?
1492 char *section, // NULL or a message/rfc822 section
1493 int flags, // various flags; see msgbase.h
1498 struct CtdlMessage *TheMessage = NULL;
1499 int retcode = CIT_OK;
1500 struct encapmsg encap;
1503 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1505 (section ? section : "<>")
1508 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1511 if (r == om_not_logged_in) {
1512 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1515 cprintf("%d An unknown error has occurred.\n", ERROR);
1522 * Check to make sure the message is actually IN this room
1524 r = check_cached_msglist(msg_num);
1525 if (r == om_access_denied) {
1526 /* Not in the cache? We get ONE shot to check it again. */
1527 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1528 r = check_cached_msglist(msg_num);
1531 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1532 msg_num, CC->room.QRname
1535 if (r == om_access_denied) {
1536 cprintf("%d message %ld was not found in this room\n",
1537 ERROR + HIGHER_ACCESS_REQUIRED,
1546 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1547 * request that we don't even bother loading the body into memory.
1549 if (headers_only == HEADERS_FAST) {
1550 TheMessage = CtdlFetchMessage(msg_num, 0);
1553 TheMessage = CtdlFetchMessage(msg_num, 1);
1556 if (TheMessage == NULL) {
1557 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1558 ERROR + MESSAGE_NOT_FOUND, msg_num);
1559 return(om_no_such_msg);
1562 /* Here is the weird form of this command, to process only an
1563 * encapsulated message/rfc822 section.
1565 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1566 memset(&encap, 0, sizeof encap);
1567 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1568 mime_parser(CM_RANGE(TheMessage, eMesageText),
1569 *extract_encapsulated_message,
1570 NULL, NULL, (void *)&encap, 0
1573 if ((Author != NULL) && (*Author == NULL))
1576 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1578 if ((Address != NULL) && (*Address == NULL))
1581 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1583 if ((MessageID != NULL) && (*MessageID == NULL))
1586 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1588 CM_Free(TheMessage);
1592 encap.msg[encap.msglen] = 0;
1593 TheMessage = convert_internet_message(encap.msg);
1594 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1596 /* Now we let it fall through to the bottom of this
1597 * function, because TheMessage now contains the
1598 * encapsulated message instead of the top-level
1599 * message. Isn't that neat?
1604 cprintf("%d msg %ld has no part %s\n",
1605 ERROR + MESSAGE_NOT_FOUND,
1609 retcode = om_no_such_msg;
1614 /* Ok, output the message now */
1615 if (retcode == CIT_OK)
1616 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1617 if ((Author != NULL) && (*Author == NULL))
1620 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1622 if ((Address != NULL) && (*Address == NULL))
1625 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1627 if ((MessageID != NULL) && (*MessageID == NULL))
1630 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1633 CM_Free(TheMessage);
1639 void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1642 char display_name[256];
1644 /* begin header processing loop for Citadel message format */
1645 safestrncpy(display_name, "<unknown>", sizeof display_name);
1646 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1647 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1648 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1649 safestrncpy(display_name, "****", sizeof display_name);
1651 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1652 safestrncpy(display_name, "anonymous", sizeof display_name);
1655 safestrncpy(display_name, buf, sizeof display_name);
1657 if ((is_room_aide())
1658 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1659 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1660 size_t tmp = strlen(display_name);
1661 snprintf(&display_name[tmp],
1662 sizeof display_name - tmp,
1667 /* Now spew the header fields in the order we like them. */
1668 for (i=0; i< NDiskFields; ++i) {
1670 Field = FieldOrder[i];
1671 if (Field != eMesageText) {
1672 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1673 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1674 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1676 if (Field == eAuthor) {
1678 cprintf("%s=%s\n", msgkeys[Field], display_name);
1681 /* Masquerade display name if needed */
1684 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1687 /* Give the client a hint about whether the message originated locally */
1688 if (Field == erFc822Addr) {
1689 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1690 cprintf("locl=yes\n"); // message originated locally.
1702 void OutputRFC822MsgHeaders(
1703 struct CtdlMessage *TheMessage,
1704 int flags, /* should the message be exported clean */
1705 const char *nl, int nlen,
1706 char *mid, long sizeof_mid,
1707 char *suser, long sizeof_suser,
1708 char *luser, long sizeof_luser,
1709 char *fuser, long sizeof_fuser,
1710 char *snode, long sizeof_snode)
1712 char datestamp[100];
1713 int subject_found = 0;
1720 for (i = 0; i < NDiskFields; ++i) {
1721 if (TheMessage->cm_fields[FieldOrder[i]]) {
1722 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1723 switch (FieldOrder[i]) {
1725 safestrncpy(luser, mptr, sizeof_luser);
1726 safestrncpy(suser, mptr, sizeof_suser);
1729 if ((flags & QP_EADDR) != 0) {
1730 mptr = qp_encode_email_addrs(mptr);
1732 sanitize_truncated_recipient(mptr);
1733 cprintf("CC: %s%s", mptr, nl);
1736 cprintf("Return-Path: %s%s", mptr, nl);
1739 cprintf("List-ID: %s%s", mptr, nl);
1742 if ((flags & QP_EADDR) != 0)
1743 mptr = qp_encode_email_addrs(mptr);
1745 while ((*hptr != '\0') && isspace(*hptr))
1747 if (!IsEmptyStr(hptr))
1748 cprintf("Envelope-To: %s%s", hptr, nl);
1751 cprintf("Subject: %s%s", mptr, nl);
1755 safestrncpy(mid, mptr, sizeof_mid);
1758 safestrncpy(fuser, mptr, sizeof_fuser);
1761 if (haschar(mptr, '@') == 0) {
1762 sanitize_truncated_recipient(mptr);
1763 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1767 if ((flags & QP_EADDR) != 0) {
1768 mptr = qp_encode_email_addrs(mptr);
1770 sanitize_truncated_recipient(mptr);
1771 cprintf("To: %s", mptr);
1776 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1777 cprintf("Date: %s%s", datestamp, nl);
1780 cprintf("References: ");
1781 k = num_tokens(mptr, '|');
1782 for (j=0; j<k; ++j) {
1783 extract_token(buf, mptr, j, '|', sizeof buf);
1784 cprintf("<%s>", buf);
1795 while ((*hptr != '\0') && isspace(*hptr))
1797 if (!IsEmptyStr(hptr))
1798 cprintf("Reply-To: %s%s", mptr, nl);
1810 /* these don't map to mime message headers. */
1813 if (mptr != mpptr) {
1818 if (subject_found == 0) {
1819 cprintf("Subject: (no subject)%s", nl);
1824 void Dump_RFC822HeadersBody(
1825 struct CtdlMessage *TheMessage,
1826 int headers_only, /* eschew the message body? */
1827 int flags, /* should the bessage be exported clean? */
1828 const char *nl, int nlen)
1830 cit_uint8_t prev_ch;
1832 const char *StartOfText = StrBufNOTNULL;
1835 int nllen = strlen(nl);
1839 mptr = TheMessage->cm_fields[eMesageText];
1842 while (*mptr != '\0') {
1843 if (*mptr == '\r') {
1850 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1852 eoh = *(mptr+1) == '\n';
1856 StartOfText = strchr(StartOfText, '\n');
1857 StartOfText = strchr(StartOfText, '\n');
1860 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1861 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1862 ((headers_only != HEADERS_NONE) &&
1863 (headers_only != HEADERS_ONLY))
1865 if (*mptr == '\n') {
1866 memcpy(&outbuf[outlen], nl, nllen);
1868 outbuf[outlen] = '\0';
1871 outbuf[outlen++] = *mptr;
1875 if (flags & ESC_DOT) {
1876 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1877 outbuf[outlen++] = '.';
1882 if (outlen > 1000) {
1883 if (client_write(outbuf, outlen) == -1) {
1884 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1887 lfSent = (outbuf[outlen - 1] == '\n');
1892 client_write(outbuf, outlen);
1893 lfSent = (outbuf[outlen - 1] == '\n');
1896 client_write(nl, nlen);
1900 /* If the format type on disk is 1 (fixed-format), then we want
1901 * everything to be output completely literally ... regardless of
1902 * what message transfer format is in use.
1904 void DumpFormatFixed(
1905 struct CtdlMessage *TheMessage,
1906 int mode, /* how would you like that message? */
1907 const char *nl, int nllen)
1915 mptr = TheMessage->cm_fields[eMesageText];
1917 if (mode == MT_MIME) {
1918 cprintf("Content-type: text/plain\n\n");
1922 while (ch = *mptr++, ch > 0) {
1926 if ((buflen > 250) && (!xlline)){
1930 while ((buflen > 0) &&
1931 (!isspace(buf[buflen])))
1937 mptr -= tbuflen - buflen;
1943 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1944 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1949 memcpy (&buf[buflen], nl, nllen);
1953 if (client_write(buf, buflen) == -1) {
1954 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1966 if (!IsEmptyStr(buf)) {
1967 cprintf("%s%s", buf, nl);
1973 * Get a message off disk. (returns om_* values found in msgbase.h)
1975 int CtdlOutputPreLoadedMsg(
1976 struct CtdlMessage *TheMessage,
1977 int mode, /* how would you like that message? */
1978 int headers_only, /* eschew the message body? */
1979 int do_proto, /* do Citadel protocol responses? */
1980 int crlf, /* Use CRLF newlines instead of LF? */
1981 int flags /* should the bessage be exported clean? */
1984 const char *nl; /* newline string */
1988 /* Buffers needed for RFC822 translation. These are all filled
1989 * using functions that are bounds-checked, and therefore we can
1990 * make them substantially smaller than SIZ.
1998 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1999 ((TheMessage == NULL) ? "NULL" : "not null"),
2000 mode, headers_only, do_proto, crlf
2003 strcpy(mid, "unknown");
2004 nl = (crlf ? "\r\n" : "\n");
2005 nlen = crlf ? 2 : 1;
2007 if (!CM_IsValidMsg(TheMessage)) {
2008 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2009 return(om_no_such_msg);
2012 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2013 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2015 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2016 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2019 /* Are we downloading a MIME component? */
2020 if (mode == MT_DOWNLOAD) {
2021 if (TheMessage->cm_format_type != FMT_RFC822) {
2023 cprintf("%d This is not a MIME message.\n",
2024 ERROR + ILLEGAL_VALUE);
2025 } else if (CC->download_fp != NULL) {
2026 if (do_proto) cprintf(
2027 "%d You already have a download open.\n",
2028 ERROR + RESOURCE_BUSY);
2030 /* Parse the message text component */
2031 mime_parser(CM_RANGE(TheMessage, eMesageText),
2032 *mime_download, NULL, NULL, NULL, 0);
2033 /* If there's no file open by this time, the requested
2034 * section wasn't found, so print an error
2036 if (CC->download_fp == NULL) {
2037 if (do_proto) cprintf(
2038 "%d Section %s not found.\n",
2039 ERROR + FILE_NOT_FOUND,
2040 CC->download_desired_section);
2043 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2046 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2047 * in a single server operation instead of opening a download file.
2049 if (mode == MT_SPEW_SECTION) {
2050 if (TheMessage->cm_format_type != FMT_RFC822) {
2052 cprintf("%d This is not a MIME message.\n",
2053 ERROR + ILLEGAL_VALUE);
2055 /* Parse the message text component */
2058 mime_parser(CM_RANGE(TheMessage, eMesageText),
2059 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2060 /* If section wasn't found, print an error
2063 if (do_proto) cprintf(
2064 "%d Section %s not found.\n",
2065 ERROR + FILE_NOT_FOUND,
2066 CC->download_desired_section);
2069 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2072 /* now for the user-mode message reading loops */
2073 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2075 /* Does the caller want to skip the headers? */
2076 if (headers_only == HEADERS_NONE) goto START_TEXT;
2078 /* Tell the client which format type we're using. */
2079 if ( (mode == MT_CITADEL) && (do_proto) ) {
2080 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2083 /* nhdr=yes means that we're only displaying headers, no body */
2084 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2085 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2088 cprintf("nhdr=yes\n");
2091 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2092 OutputCtdlMsgHeaders(TheMessage, do_proto);
2095 /* begin header processing loop for RFC822 transfer format */
2100 if (mode == MT_RFC822)
2101 OutputRFC822MsgHeaders(
2106 suser, sizeof(suser),
2107 luser, sizeof(luser),
2108 fuser, sizeof(fuser),
2109 snode, sizeof(snode)
2113 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2114 suser[i] = tolower(suser[i]);
2115 if (!isalnum(suser[i])) suser[i]='_';
2118 if (mode == MT_RFC822) {
2119 /* Construct a fun message id */
2120 cprintf("Message-ID: <%s", mid);
2121 if (strchr(mid, '@')==NULL) {
2122 cprintf("@%s", snode);
2126 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2127 cprintf("From: \"----\" <x@x.org>%s", nl);
2129 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2130 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2132 else if (!IsEmptyStr(fuser)) {
2133 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2136 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2139 /* Blank line signifying RFC822 end-of-headers */
2140 if (TheMessage->cm_format_type != FMT_RFC822) {
2145 /* end header processing loop ... at this point, we're in the text */
2147 if (headers_only == HEADERS_FAST) goto DONE;
2149 /* Tell the client about the MIME parts in this message */
2150 if (TheMessage->cm_format_type == FMT_RFC822) {
2151 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2152 memset(&ma, 0, sizeof(struct ma_info));
2153 mime_parser(CM_RANGE(TheMessage, eMesageText),
2154 (do_proto ? *list_this_part : NULL),
2155 (do_proto ? *list_this_pref : NULL),
2156 (do_proto ? *list_this_suff : NULL),
2159 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2160 Dump_RFC822HeadersBody(
2169 if (headers_only == HEADERS_ONLY) {
2173 /* signify start of msg text */
2174 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2175 if (do_proto) cprintf("text\n");
2178 if (TheMessage->cm_format_type == FMT_FIXED)
2181 mode, /* how would you like that message? */
2184 /* If the message on disk is format 0 (Citadel vari-format), we
2185 * output using the formatter at 80 columns. This is the final output
2186 * form if the transfer format is RFC822, but if the transfer format
2187 * is Citadel proprietary, it'll still work, because the indentation
2188 * for new paragraphs is correct and the client will reformat the
2189 * message to the reader's screen width.
2191 if (TheMessage->cm_format_type == FMT_CITADEL) {
2192 if (mode == MT_MIME) {
2193 cprintf("Content-type: text/x-citadel-variformat\n\n");
2195 memfmout(TheMessage->cm_fields[eMesageText], nl);
2198 /* If the message on disk is format 4 (MIME), we've gotta hand it
2199 * off to the MIME parser. The client has already been told that
2200 * this message is format 1 (fixed format), so the callback function
2201 * we use will display those parts as-is.
2203 if (TheMessage->cm_format_type == FMT_RFC822) {
2204 memset(&ma, 0, sizeof(struct ma_info));
2206 if (mode == MT_MIME) {
2207 ma.use_fo_hooks = 0;
2208 strcpy(ma.chosen_part, "1");
2209 ma.chosen_pref = 9999;
2210 ma.dont_decode = CC->msg4_dont_decode;
2211 mime_parser(CM_RANGE(TheMessage, eMesageText),
2212 *choose_preferred, *fixed_output_pre,
2213 *fixed_output_post, (void *)&ma, 1);
2214 mime_parser(CM_RANGE(TheMessage, eMesageText),
2215 *output_preferred, NULL, NULL, (void *)&ma, 1);
2218 ma.use_fo_hooks = 1;
2219 mime_parser(CM_RANGE(TheMessage, eMesageText),
2220 *fixed_output, *fixed_output_pre,
2221 *fixed_output_post, (void *)&ma, 0);
2226 DONE: /* now we're done */
2227 if (do_proto) cprintf("000\n");
2232 * Save one or more message pointers into a specified room
2233 * (Returns 0 for success, nonzero for failure)
2234 * roomname may be NULL to use the current room
2236 * Note that the 'supplied_msg' field may be set to NULL, in which case
2237 * the message will be fetched from disk, by number, if we need to perform
2238 * replication checks. This adds an additional database read, so if the
2239 * caller already has the message in memory then it should be supplied. (Obviously
2240 * this mode of operation only works if we're saving a single message.)
2242 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2243 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2246 char hold_rm[ROOMNAMELEN];
2247 struct cdbdata *cdbfr;
2250 long highest_msg = 0L;
2253 struct CtdlMessage *msg = NULL;
2255 long *msgs_to_be_merged = NULL;
2256 int num_msgs_to_be_merged = 0;
2259 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2260 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2263 strcpy(hold_rm, CC->room.QRname);
2266 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2267 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2268 if (num_newmsgs > 1) supplied_msg = NULL;
2270 /* Now the regular stuff */
2271 if (CtdlGetRoomLock(&CC->room,
2272 ((roomname != NULL) ? roomname : CC->room.QRname) )
2274 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2275 return(ERROR + ROOM_NOT_FOUND);
2279 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2280 num_msgs_to_be_merged = 0;
2283 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2284 if (cdbfr == NULL) {
2288 msglist = (long *) cdbfr->ptr;
2289 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2290 num_msgs = cdbfr->len / sizeof(long);
2295 /* Create a list of msgid's which were supplied by the caller, but do
2296 * not already exist in the target room. It is absolutely taboo to
2297 * have more than one reference to the same message in a room.
2299 for (i=0; i<num_newmsgs; ++i) {
2301 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2302 if (msglist[j] == newmsgidlist[i]) {
2307 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2311 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2314 * Now merge the new messages
2316 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2317 if (msglist == NULL) {
2318 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2319 free(msgs_to_be_merged);
2320 return (ERROR + INTERNAL_ERROR);
2322 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2323 num_msgs += num_msgs_to_be_merged;
2325 /* Sort the message list, so all the msgid's are in order */
2326 num_msgs = sort_msglist(msglist, num_msgs);
2328 /* Determine the highest message number */
2329 highest_msg = msglist[num_msgs - 1];
2331 /* Write it back to disk. */
2332 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2333 msglist, (int)(num_msgs * sizeof(long)));
2335 /* Free up the memory we used. */
2338 /* Update the highest-message pointer and unlock the room. */
2339 CC->room.QRhighest = highest_msg;
2340 CtdlPutRoomLock(&CC->room);
2342 /* Perform replication checks if necessary */
2343 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2344 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2346 for (i=0; i<num_msgs_to_be_merged; ++i) {
2347 msgid = msgs_to_be_merged[i];
2349 if (supplied_msg != NULL) {
2353 msg = CtdlFetchMessage(msgid, 0);
2357 ReplicationChecks(msg);
2359 /* If the message has an Exclusive ID, index that... */
2360 if (!CM_IsEmpty(msg, eExclusiveID)) {
2361 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2364 /* Free up the memory we may have allocated */
2365 if (msg != supplied_msg) {
2374 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2377 /* Submit this room for processing by hooks */
2378 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2379 if (total_roomhook_errors) {
2380 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2383 /* Go back to the room we were in before we wandered here... */
2384 CtdlGetRoom(&CC->room, hold_rm);
2386 /* Bump the reference count for all messages which were merged */
2387 if (!suppress_refcount_adj) {
2388 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2391 /* Free up memory... */
2392 if (msgs_to_be_merged != NULL) {
2393 free(msgs_to_be_merged);
2396 /* Return success. */
2402 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2405 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2406 int do_repl_check, struct CtdlMessage *supplied_msg)
2408 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2413 * Message base operation to save a new message to the message store
2414 * (returns new message number)
2416 * This is the back end for CtdlSubmitMsg() and should not be directly
2417 * called by server-side modules.
2420 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2428 * If the message is big, set its body aside for storage elsewhere
2429 * and we hide the message body from the serializer
2431 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2433 holdM = msg->cm_fields[eMesageText];
2434 msg->cm_fields[eMesageText] = NULL;
2435 holdMLen = msg->cm_lengths[eMesageText];
2436 msg->cm_lengths[eMesageText] = 0;
2439 /* Serialize our data structure for storage in the database */
2440 CtdlSerializeMessage(&smr, msg);
2443 /* put the message body back into the message */
2444 msg->cm_fields[eMesageText] = holdM;
2445 msg->cm_lengths[eMesageText] = holdMLen;
2450 cprintf("%d Unable to serialize message\n",
2451 ERROR + INTERNAL_ERROR);
2454 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2460 /* Write our little bundle of joy into the message base */
2461 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2463 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2467 retval = cdb_store(CDB_BIGMSGS,
2474 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2479 /* Free the memory we used for the serialized message */
2486 long send_message(struct CtdlMessage *msg) {
2492 /* Get a new message number */
2493 newmsgid = get_new_message_number();
2495 /* Generate an ID if we don't have one already */
2496 if (CM_IsEmpty(msg, emessageId)) {
2497 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2498 (long unsigned int) time(NULL),
2499 (long unsigned int) newmsgid,
2500 CtdlGetConfigStr("c_fqdn")
2503 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2506 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2512 /* Return the *local* message ID to the caller
2513 * (even if we're storing an incoming network message)
2520 * Serialize a struct CtdlMessage into the format used on disk.
2522 * This function loads up a "struct ser_ret" (defined in server.h) which
2523 * contains the length of the serialized message and a pointer to the
2524 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2526 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2527 struct CtdlMessage *msg) /* unserialized msg */
2533 * Check for valid message format
2535 if (CM_IsValidMsg(msg) == 0) {
2536 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2543 for (i=0; i < NDiskFields; ++i)
2544 if (msg->cm_fields[FieldOrder[i]] != NULL)
2545 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2547 ret->ser = malloc(ret->len);
2548 if (ret->ser == NULL) {
2549 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2556 ret->ser[1] = msg->cm_anon_type;
2557 ret->ser[2] = msg->cm_format_type;
2560 for (i=0; i < NDiskFields; ++i) {
2561 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2562 ret->ser[wlen++] = (char)FieldOrder[i];
2564 memcpy(&ret->ser[wlen],
2565 msg->cm_fields[FieldOrder[i]],
2566 msg->cm_lengths[FieldOrder[i]] + 1);
2568 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2572 if (ret->len != wlen) {
2573 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2581 * Check to see if any messages already exist in the current room which
2582 * carry the same Exclusive ID as this one. If any are found, delete them.
2584 void ReplicationChecks(struct CtdlMessage *msg) {
2585 long old_msgnum = (-1L);
2587 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2589 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2591 /* No exclusive id? Don't do anything. */
2592 if (msg == NULL) return;
2593 if (CM_IsEmpty(msg, eExclusiveID)) return;
2595 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2596 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2598 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2599 if (old_msgnum > 0L) {
2600 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2601 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2607 * Save a message to disk and submit it into the delivery system.
2609 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2610 struct recptypes *recps, /* recipients (if mail) */
2611 const char *force /* force a particular room? */
2613 char hold_rm[ROOMNAMELEN];
2614 char actual_rm[ROOMNAMELEN];
2615 char force_room[ROOMNAMELEN];
2616 char content_type[SIZ]; /* We have to learn this */
2617 char recipient[SIZ];
2618 char bounce_to[1024];
2621 const char *mptr = NULL;
2622 struct ctdluser userbuf;
2624 struct MetaData smi;
2625 char *collected_addresses = NULL;
2626 struct addresses_to_be_filed *aptr = NULL;
2627 StrBuf *saved_rfc822_version = NULL;
2628 int qualified_for_journaling = 0;
2630 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2631 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2633 /* If this message has no timestamp, we take the liberty of
2634 * giving it one, right now.
2636 if (CM_IsEmpty(msg, eTimestamp)) {
2637 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2640 /* If this message has no path, we generate one.
2642 if (CM_IsEmpty(msg, eMessagePath)) {
2643 if (!CM_IsEmpty(msg, eAuthor)) {
2644 CM_CopyField(msg, eMessagePath, eAuthor);
2645 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2646 if (isspace(msg->cm_fields[eMessagePath][a])) {
2647 msg->cm_fields[eMessagePath][a] = ' ';
2652 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2656 if (force == NULL) {
2657 force_room[0] = '\0';
2660 strcpy(force_room, force);
2663 /* Learn about what's inside, because it's what's inside that counts */
2664 if (CM_IsEmpty(msg, eMesageText)) {
2665 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2669 switch (msg->cm_format_type) {
2671 strcpy(content_type, "text/x-citadel-variformat");
2674 strcpy(content_type, "text/plain");
2677 strcpy(content_type, "text/plain");
2678 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2681 safestrncpy(content_type, &mptr[13], sizeof content_type);
2682 striplt(content_type);
2683 aptr = content_type;
2684 while (!IsEmptyStr(aptr)) {
2696 /* Goto the correct room */
2697 room = (recps) ? CC->room.QRname : SENTITEMS;
2698 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2699 strcpy(hold_rm, CC->room.QRname);
2700 strcpy(actual_rm, CC->room.QRname);
2701 if (recps != NULL) {
2702 strcpy(actual_rm, SENTITEMS);
2705 /* If the user is a twit, move to the twit room for posting */
2707 if (CC->user.axlevel == AxProbU) {
2708 strcpy(hold_rm, actual_rm);
2709 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2710 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2714 /* ...or if this message is destined for Aide> then go there. */
2715 if (!IsEmptyStr(force_room)) {
2716 strcpy(actual_rm, force_room);
2719 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2720 if (strcasecmp(actual_rm, CC->room.QRname)) {
2721 /* CtdlGetRoom(&CC->room, actual_rm); */
2722 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2726 * If this message has no O (room) field, generate one.
2728 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2729 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2732 /* Perform "before save" hooks (aborting if any return nonzero) */
2733 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2734 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2737 * If this message has an Exclusive ID, and the room is replication
2738 * checking enabled, then do replication checks.
2740 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2741 ReplicationChecks(msg);
2744 /* Save it to disk */
2745 syslog(LOG_DEBUG, "msgbase: saving to disk");
2746 newmsgid = send_message(msg);
2747 if (newmsgid <= 0L) return(-5);
2749 /* Write a supplemental message info record. This doesn't have to
2750 * be a critical section because nobody else knows about this message
2753 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2754 memset(&smi, 0, sizeof(struct MetaData));
2755 smi.meta_msgnum = newmsgid;
2756 smi.meta_refcount = 0;
2757 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2760 * Measure how big this message will be when rendered as RFC822.
2761 * We do this for two reasons:
2762 * 1. We need the RFC822 length for the new metadata record, so the
2763 * POP and IMAP services don't have to calculate message lengths
2764 * while the user is waiting (multiplied by potentially hundreds
2765 * or thousands of messages).
2766 * 2. If journaling is enabled, we will need an RFC822 version of the
2767 * message to attach to the journalized copy.
2769 if (CC->redirect_buffer != NULL) {
2770 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2773 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2774 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2775 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2776 saved_rfc822_version = CC->redirect_buffer;
2777 CC->redirect_buffer = NULL;
2781 /* Now figure out where to store the pointers */
2782 syslog(LOG_DEBUG, "msgbase: storing pointers");
2784 /* If this is being done by the networker delivering a private
2785 * message, we want to BYPASS saving the sender's copy (because there
2786 * is no local sender; it would otherwise go to the Trashcan).
2788 if ((!CC->internal_pgm) || (recps == NULL)) {
2789 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2790 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2791 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2795 /* For internet mail, drop a copy in the outbound queue room */
2796 if ((recps != NULL) && (recps->num_internet > 0)) {
2797 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2800 /* If other rooms are specified, drop them there too. */
2801 if ((recps != NULL) && (recps->num_room > 0)) {
2802 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2803 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2804 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2805 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2809 /* Bump this user's messages posted counter. */
2810 syslog(LOG_DEBUG, "msgbase: updating user");
2811 CtdlLockGetCurrentUser();
2812 CC->user.posted = CC->user.posted + 1;
2813 CtdlPutCurrentUserLock();
2815 /* Decide where bounces need to be delivered */
2816 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2817 if (CC->logged_in) {
2818 strcpy(bounce_to, CC->user.fullname);
2820 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2821 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2823 recps->bounce_to = bounce_to;
2826 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2828 /* If this is private, local mail, make a copy in the
2829 * recipient's mailbox and bump the reference count.
2831 if ((recps != NULL) && (recps->num_local > 0)) {
2835 pch = recps->recp_local;
2836 recps->recp_local = recipient;
2837 ntokens = num_tokens(pch, '|');
2838 for (i=0; i<ntokens; ++i) {
2839 extract_token(recipient, pch, i, '|', sizeof recipient);
2840 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2841 if (CtdlGetUser(&userbuf, recipient) == 0) {
2842 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2843 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2844 CtdlBumpNewMailCounter(userbuf.usernum);
2845 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2848 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2849 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2852 recps->recp_local = pch;
2855 /* Perform "after save" hooks */
2856 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2858 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2859 CM_FlushField(msg, eVltMsgNum);
2861 /* Go back to the room we started from */
2862 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2863 if (strcasecmp(hold_rm, CC->room.QRname)) {
2864 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2868 * Any addresses to harvest for someone's address book?
2870 if ( (CC->logged_in) && (recps != NULL) ) {
2871 collected_addresses = harvest_collected_addresses(msg);
2874 if (collected_addresses != NULL) {
2875 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2876 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2877 aptr->roomname = strdup(actual_rm);
2878 aptr->collected_addresses = collected_addresses;
2879 begin_critical_section(S_ATBF);
2882 end_critical_section(S_ATBF);
2886 * Determine whether this message qualifies for journaling.
2888 if (!CM_IsEmpty(msg, eJournal)) {
2889 qualified_for_journaling = 0;
2892 if (recps == NULL) {
2893 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2895 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2896 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2899 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2904 * Do we have to perform journaling? If so, hand off the saved
2905 * RFC822 version will be handed off to the journaler for background
2906 * submit. Otherwise, we have to free the memory ourselves.
2908 if (saved_rfc822_version != NULL) {
2909 if (qualified_for_journaling) {
2910 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2913 FreeStrBuf(&saved_rfc822_version);
2917 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2918 recps->bounce_to = NULL;
2926 * Convenience function for generating small administrative messages.
2928 long quickie_message(const char *from,
2929 const char *fromaddr,
2934 const char *subject)
2936 struct CtdlMessage *msg;
2937 struct recptypes *recp = NULL;
2939 msg = malloc(sizeof(struct CtdlMessage));
2940 memset(msg, 0, sizeof(struct CtdlMessage));
2941 msg->cm_magic = CTDLMESSAGE_MAGIC;
2942 msg->cm_anon_type = MES_NORMAL;
2943 msg->cm_format_type = format_type;
2945 if (!IsEmptyStr(from)) {
2946 CM_SetField(msg, eAuthor, from, -1);
2948 else if (!IsEmptyStr(fromaddr)) {
2950 CM_SetField(msg, eAuthor, fromaddr, -1);
2951 pAt = strchr(msg->cm_fields[eAuthor], '@');
2953 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2957 msg->cm_fields[eAuthor] = strdup("Citadel");
2960 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
2961 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
2962 if (!IsEmptyStr(to)) {
2963 CM_SetField(msg, eRecipient, to, -1);
2964 recp = validate_recipients(to, NULL, 0);
2966 if (!IsEmptyStr(subject)) {
2967 CM_SetField(msg, eMsgSubject, subject, -1);
2969 if (!IsEmptyStr(text)) {
2970 CM_SetField(msg, eMesageText, text, -1);
2973 long msgnum = CtdlSubmitMsg(msg, recp, room);
2975 if (recp != NULL) free_recipients(recp);
2981 * Back end function used by CtdlMakeMessage() and similar functions
2983 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
2985 size_t maxlen, /* maximum message length */
2986 StrBuf *exist, /* if non-null, append to it;
2987 exist is ALWAYS freed */
2988 int crlf /* CRLF newlines instead of LF */
2996 LineBuf = NewStrBufPlain(NULL, SIZ);
2997 if (exist == NULL) {
2998 Message = NewStrBufPlain(NULL, 4 * SIZ);
3001 Message = NewStrBufDup(exist);
3004 /* Do we need to change leading ".." to "." for SMTP escaping? */
3005 if ((tlen == 1) && (*terminator == '.')) {
3009 /* read in the lines of message text one by one */
3011 if (CtdlClientGetLine(LineBuf) < 0) {
3014 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3017 if ( (!flushing) && (!finished) ) {
3019 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3022 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3025 /* Unescape SMTP-style input of two dots at the beginning of the line */
3026 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3027 StrBufCutLeft(LineBuf, 1);
3029 StrBufAppendBuf(Message, LineBuf, 0);
3032 /* if we've hit the max msg length, flush the rest */
3033 if (StrLength(Message) >= maxlen) flushing = 1;
3035 } while (!finished);
3036 FreeStrBuf(&LineBuf);
3042 * Back end function used by CtdlMakeMessage() and similar functions
3044 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3046 size_t maxlen, /* maximum message length */
3047 StrBuf *exist, /* if non-null, append to it;
3048 exist is ALWAYS freed */
3049 int crlf /* CRLF newlines instead of LF */
3054 Message = CtdlReadMessageBodyBuf(terminator,
3060 if (Message == NULL)
3063 return SmashStrBuf(&Message);
3067 struct CtdlMessage *CtdlMakeMessage(
3068 struct ctdluser *author, /* author's user structure */
3069 char *recipient, /* NULL if it's not mail */
3070 char *recp_cc, /* NULL if it's not mail */
3071 char *room, /* room where it's going */
3072 int type, /* see MES_ types in header file */
3073 int format_type, /* variformat, plain text, MIME... */
3074 char *fake_name, /* who we're masquerading as */
3075 char *my_email, /* which of my email addresses to use (empty is ok) */
3076 char *subject, /* Subject (optional) */
3077 char *supplied_euid, /* ...or NULL if this is irrelevant */
3078 char *preformatted_text, /* ...or NULL to read text from client */
3079 char *references /* Thread references */
3081 return CtdlMakeMessageLen(
3082 author, /* author's user structure */
3083 recipient, /* NULL if it's not mail */
3084 (recipient)?strlen(recipient) : 0,
3085 recp_cc, /* NULL if it's not mail */
3086 (recp_cc)?strlen(recp_cc): 0,
3087 room, /* room where it's going */
3088 (room)?strlen(room): 0,
3089 type, /* see MES_ types in header file */
3090 format_type, /* variformat, plain text, MIME... */
3091 fake_name, /* who we're masquerading as */
3092 (fake_name)?strlen(fake_name): 0,
3093 my_email, /* which of my email addresses to use (empty is ok) */
3094 (my_email)?strlen(my_email): 0,
3095 subject, /* Subject (optional) */
3096 (subject)?strlen(subject): 0,
3097 supplied_euid, /* ...or NULL if this is irrelevant */
3098 (supplied_euid)?strlen(supplied_euid):0,
3099 preformatted_text, /* ...or NULL to read text from client */
3100 (preformatted_text)?strlen(preformatted_text) : 0,
3101 references, /* Thread references */
3102 (references)?strlen(references):0);
3108 * Build a binary message to be saved on disk.
3109 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3110 * will become part of the message. This means you are no longer
3111 * responsible for managing that memory -- it will be freed along with
3112 * the rest of the fields when CM_Free() is called.)
3114 struct CtdlMessage *CtdlMakeMessageLen(
3115 struct ctdluser *author, /* author's user structure */
3116 char *recipient, /* NULL if it's not mail */
3118 char *recp_cc, /* NULL if it's not mail */
3120 char *room, /* room where it's going */
3122 int type, /* see MES_ types in header file */
3123 int format_type, /* variformat, plain text, MIME... */
3124 char *fake_name, /* who we're masquerading as */
3126 char *my_email, /* which of my email addresses to use (empty is ok) */
3128 char *subject, /* Subject (optional) */
3130 char *supplied_euid, /* ...or NULL if this is irrelevant */
3132 char *preformatted_text, /* ...or NULL to read text from client */
3134 char *references, /* Thread references */
3139 struct CtdlMessage *msg;
3141 StrBuf *FakeEncAuthor = NULL;
3143 msg = malloc(sizeof(struct CtdlMessage));
3144 memset(msg, 0, sizeof(struct CtdlMessage));
3145 msg->cm_magic = CTDLMESSAGE_MAGIC;
3146 msg->cm_anon_type = type;
3147 msg->cm_format_type = format_type;
3149 if (recipient != NULL) rcplen = striplt(recipient);
3150 if (recp_cc != NULL) cclen = striplt(recp_cc);
3152 /* Path or Return-Path */
3154 CM_SetField(msg, eMessagePath, my_email, myelen);
3156 else if (!IsEmptyStr(author->fullname)) {
3157 CM_SetField(msg, eMessagePath, author->fullname, -1);
3159 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3161 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3162 CM_SetField(msg, eTimestamp, buf, blen);
3165 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3168 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3170 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3171 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3172 FreeStrBuf(&FakeAuthor);
3174 if (!!IsEmptyStr(CC->room.QRname)) {
3175 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3176 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3179 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3184 CM_SetField(msg, eRecipient, recipient, rcplen);
3187 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3191 CM_SetField(msg, erFc822Addr, my_email, myelen);
3193 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3194 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3197 if (subject != NULL) {
3199 length = striplt(subject);
3205 while ((subject[i] != '\0') &&
3206 (IsAscii = isascii(subject[i]) != 0 ))
3209 CM_SetField(msg, eMsgSubject, subject, subjlen);
3210 else /* ok, we've got utf8 in the string. */
3213 rfc2047Subj = rfc2047encode(subject, length);
3214 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3221 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3225 CM_SetField(msg, eWeferences, references, reflen);
3228 if (preformatted_text != NULL) {
3229 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3233 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3234 if (MsgBody != NULL) {
3235 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3244 * API function to delete messages which match a set of criteria
3245 * (returns the actual number of messages deleted)
3247 int CtdlDeleteMessages(const char *room_name, // which room
3248 long *dmsgnums, // array of msg numbers to be deleted
3249 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3250 char *content_type // or "" for any. regular expressions expected.
3252 struct ctdlroom qrbuf;
3253 struct cdbdata *cdbfr;
3254 long *msglist = NULL;
3255 long *dellist = NULL;
3258 int num_deleted = 0;
3260 struct MetaData smi;
3263 int need_to_free_re = 0;
3265 if (content_type) if (!IsEmptyStr(content_type)) {
3266 regcomp(&re, content_type, 0);
3267 need_to_free_re = 1;
3269 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3271 /* get room record, obtaining a lock... */
3272 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3273 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3274 if (need_to_free_re) regfree(&re);
3275 return(0); /* room not found */
3277 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3279 if (cdbfr != NULL) {
3280 dellist = malloc(cdbfr->len);
3281 msglist = (long *) cdbfr->ptr;
3282 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3283 num_msgs = cdbfr->len / sizeof(long);
3287 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3288 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3289 int have_more_del = 1;
3291 num_msgs = sort_msglist(msglist, num_msgs);
3292 if (num_dmsgnums > 1)
3293 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3296 StrBuf *dbg = NewStrBuf();
3297 for (i = 0; i < num_dmsgnums; i++)
3298 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3299 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3304 while ((i < num_msgs) && (have_more_del)) {
3307 /* Set/clear a bit for each criterion */
3309 /* 0 messages in the list or a null list means that we are
3310 * interested in deleting any messages which meet the other criteria.
3313 delete_this |= 0x01;
3316 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3321 if (msglist[i] == dmsgnums[j]) {
3322 delete_this |= 0x01;
3325 have_more_del = (j < num_dmsgnums);
3328 if (have_contenttype) {
3329 GetMetaData(&smi, msglist[i]);
3330 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3331 delete_this |= 0x02;
3334 delete_this |= 0x02;
3337 /* Delete message only if all bits are set */
3338 if (delete_this == 0x03) {
3339 dellist[num_deleted++] = msglist[i];
3346 StrBuf *dbg = NewStrBuf();
3347 for (i = 0; i < num_deleted; i++)
3348 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3349 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3353 num_msgs = sort_msglist(msglist, num_msgs);
3354 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3355 msglist, (int)(num_msgs * sizeof(long)));
3358 qrbuf.QRhighest = msglist[num_msgs - 1];
3360 qrbuf.QRhighest = 0;
3362 CtdlPutRoomLock(&qrbuf);
3364 /* Go through the messages we pulled out of the index, and decrement
3365 * their reference counts by 1. If this is the only room the message
3366 * was in, the reference count will reach zero and the message will
3367 * automatically be deleted from the database. We do this in a
3368 * separate pass because there might be plug-in hooks getting called,
3369 * and we don't want that happening during an S_ROOMS critical
3373 for (i=0; i<num_deleted; ++i) {
3374 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3376 AdjRefCountList(dellist, num_deleted, -1);
3378 /* Now free the memory we used, and go away. */
3379 if (msglist != NULL) free(msglist);
3380 if (dellist != NULL) free(dellist);
3381 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3382 if (need_to_free_re) regfree(&re);
3383 return (num_deleted);
3388 * GetMetaData() - Get the supplementary record for a message
3390 void GetMetaData(struct MetaData *smibuf, long msgnum)
3392 struct cdbdata *cdbsmi;
3395 memset(smibuf, 0, sizeof(struct MetaData));
3396 smibuf->meta_msgnum = msgnum;
3397 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3399 /* Use the negative of the message number for its supp record index */
3400 TheIndex = (0L - msgnum);
3402 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3403 if (cdbsmi == NULL) {
3404 return; /* record not found; leave it alone */
3406 memcpy(smibuf, cdbsmi->ptr,
3407 ((cdbsmi->len > sizeof(struct MetaData)) ?
3408 sizeof(struct MetaData) : cdbsmi->len)
3416 * PutMetaData() - (re)write supplementary record for a message
3418 void PutMetaData(struct MetaData *smibuf)
3422 /* Use the negative of the message number for the metadata db index */
3423 TheIndex = (0L - smibuf->meta_msgnum);
3425 cdb_store(CDB_MSGMAIN,
3426 &TheIndex, (int)sizeof(long),
3427 smibuf, (int)sizeof(struct MetaData)
3433 * Convenience function to process a big block of AdjRefCount() operations
3435 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3439 for (i = 0; i < nmsg; i++) {
3440 AdjRefCount(msgnum[i], incr);
3446 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3448 void AdjRefCount(long msgnum, int incr)
3450 struct MetaData smi;
3453 /* This is a *tight* critical section; please keep it that way, as
3454 * it may get called while nested in other critical sections.
3455 * Complicating this any further will surely cause deadlock!
3457 begin_critical_section(S_SUPPMSGMAIN);
3458 GetMetaData(&smi, msgnum);
3459 smi.meta_refcount += incr;
3461 end_critical_section(S_SUPPMSGMAIN);
3462 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3464 /* If the reference count is now zero, delete both the message and its metadata record.
3466 if (smi.meta_refcount == 0) {
3467 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3469 /* Call delete hooks with NULL room to show it has gone altogether */
3470 PerformDeleteHooks(NULL, msgnum);
3472 /* Remove from message base */
3474 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3475 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3477 /* Remove metadata record */
3478 delnum = (0L - msgnum);
3479 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3485 * Write a generic object to this room
3487 * Note: this could be much more efficient. Right now we use two temporary
3488 * files, and still pull the message into memory as with all others.
3490 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3491 char *content_type, /* MIME type of this object */
3492 char *raw_message, /* Data to be written */
3493 off_t raw_length, /* Size of raw_message */
3494 struct ctdluser *is_mailbox, /* Mailbox room? */
3495 int is_binary, /* Is encoding necessary? */
3496 int is_unique, /* Del others of this type? */
3497 unsigned int flags /* Internal save flags */
3499 struct ctdlroom qrbuf;
3500 char roomname[ROOMNAMELEN];
3501 struct CtdlMessage *msg;
3502 StrBuf *encoded_message = NULL;
3504 if (is_mailbox != NULL) {
3505 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3508 safestrncpy(roomname, req_room, sizeof(roomname));
3511 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3514 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3517 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3520 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3521 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3522 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3525 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3528 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3532 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3535 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3538 syslog(LOG_DEBUG, "msgbase: allocating");
3539 msg = malloc(sizeof(struct CtdlMessage));
3540 memset(msg, 0, sizeof(struct CtdlMessage));
3541 msg->cm_magic = CTDLMESSAGE_MAGIC;
3542 msg->cm_anon_type = MES_NORMAL;
3543 msg->cm_format_type = 4;
3544 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3545 CM_SetField(msg, eOriginalRoom, req_room, -1);
3546 msg->cm_flags = flags;
3548 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3550 /* Create the requested room if we have to. */
3551 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3552 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3554 /* If the caller specified this object as unique, delete all
3555 * other objects of this type that are currently in the room.
3558 syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
3559 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3562 /* Now write the data */
3563 CtdlSubmitMsg(msg, NULL, roomname);
3568 /************************************************************************/
3569 /* MODULE INITIALIZATION */
3570 /************************************************************************/
3572 CTDL_MODULE_INIT(msgbase)
3575 FillMsgKeyLookupTable();
3578 /* return our module id for the log */